当前位置: 首页 > 后端技术 > Java

什么是单元测试?为什么要这样做?

时间:2023-04-01 18:51:56 Java

什么是UT?UT(UnitTest)即单元测试UT的值是什么?大多数开发人员不喜欢编写UT。原因如下:产品经理天天提醒进度,我怎么写UTUT测试自己?代码,自测?QA有什么用?自测能查出bug吗?都是凭自己的想法,就像考试后的第一、二次检查一样,基本什么都查不出来。UT维护成本太高,投入产出比太低写UT。总之,有无数理由不写。UT,作为一个工作不到三年的菜鸟,深有体会。我在大众点评工作的时候,团队的“UT”都集中在RPC服务器上。为什么双引号?因为RPC服务器没有功能测试的页面,部署到测试环境测试太麻烦,所以只能写UT。在这种场景下,我觉得称其为“验证”更为合适。验证不等于测试。验证往往只写主要逻辑是否通过,而且只有一个Case,没有Assert,有的是System.out。我实习的时候做过测试,当时就知道一个测试模型。如下图所示:图中的意思是底越低测试效果越好,越高越差。也就是说,现在大部分公司做的功能测试其实是最差的测试方法。另外QA界有一个场景:大家都知道功能测试没有技术含量,那么如何让自己脱颖而出呢?答案是:自动化测试。现实情况是,很少有公司能把自动化测试做好,百度是业内比较好的一家。那么为什么自动化测试这么难做呢?在这个模型中,黑盒越高,自动化测试的难度就越大。这句话反过来说自动化测试越往下越好?是的,UT其实是最简单有效的自动化测试。所以很多公司都有一个场景:QA写UT。原因可以归结为两点:开发不愿意写UT,QA想自动化测试来解放自己。上述模型只是从理论上说明UT具有很大的价值,但事实真的如此吗?我只想说,只有真正尝过UT好处的人,才会体会到UT的价值。UnitTest&IntegrationTest单元测试和集成测试之间的界限我相信大多数开发都不清楚。个人理解单元测试针对的是业务逻辑的最小单元,过于抽象。物理上可以简单理解为类的方法,可以是公有方法,也可以是私有方法。单元测试不应该包含外部依赖的逻辑,否则就是集成测试。这就是问题的核心所在。一个服务的接口实现可能依赖很多第三方:1.其他本地services2.dao调用3.rpc调用4.微服务调用。如下图所示:也就是说,你的单元测试其实调用的是外部依赖,也就是集成测试。这其实很常见吧?先说说本案例中如何集成测试。LocalIntegrationTest本地集成测试是指不依赖于其他进程。包括:服务依赖其他本地服务或daos的情况。在谈如何集成测试之前,我们先来看看测试模型。测试主要包括三个部分:1.数据准备2.执行逻辑3.输出验证。第一步:数据准备在本地集成测试中,数据源基本来自dao,dao来自sql。也就是在执行一个case之前,先执行一些sql脚本,数据库使用内存数据库如h2。切记不要依赖公司测试环境的db。下图是一个使用spring-test框架的案例,可以在案例执行前准备好我们需要的各种数据。另外,案例执行完毕后,执行clean.sql脚本清理脏数据。也说明一个案例的执行环境是完全独立的,案例之间互不干扰是非常重要的。第二步:执行逻辑最简单,调用我们测试的方法即可。第三步:验证集成测试一般调用service或者dao接口验证。例如:CRUD操作集成测试调用C接口调用R接口,验证C成功调用U接口调用R接口,验证U成功调用D接口调用R接口,并验证D是否成功。RemoteIntegrationTest假设我们的服务实现依赖于某个RPC服务的第一步:有多少条数据要插入到别人的数据库中?或者和PRCService的Owner商量搭建测试环境让我们测试?有的公司真的有专门的自动化测试环境,那么即使有测试环境,第三方服务如何在各种case场景下返回数据给我们呢?想想就心痛。第二步:执行方法假设我们已经成功解决了第一步的问题,那大家都高兴。现在我们来看第二步。假设我们的服务调用了另一个RPC服务创建了很多数据,跑了无数个case。这样一来,RPCService对应的数据库就是我们的脏数据。如何清洗?他们敢随便删除数据吗?想想就心痛。第3步:输出验证假设我们再次愉快地解决了第2步中的问题。现在我们来看第三步。假设我们的方法执行的最终输出是创建一个订单。当然,订单是调用订单服务接口的,那么我们如何验证订单是否创建成功呢?或许可以调用订单服务查询订单的接口来验证。显然大多数情况都不是那么完美。想想就心痛。通过以上分析,LocalIntegrationTest是可行的,RemoteIntegrationTest基本不可行。那么有没有办法解决呢?答案是模拟第1步:模拟RPC服务返回它想要返回的任何数据。第二步:依然是Mock接口,想调用多少次就调用多少次。第三步:这一步等下面的单元测试完了就明白了。上面我们提到了Mock可以解决外部依赖的问题。现在有很多Mock的开源框架比如mockito。那么问题来了,既然我们可以mock第三方远程依赖,为什么不mockdao和本地服务呢?没错,所有的外部依赖都被mock掉了,这就是单元测试。因为我们只关心被测方法的业务逻辑,即高内聚的逻辑单元。如下图所示:好处是:没有不能创建的数据,返回Mock的对象代码中的所有异常处理代码也可以使用mock接口使其抛出异常,不会产生任何dirty数据和运行案例更快,因为不需要启动整个项目,这相当于Main方法。有人会说,这都是被嘲笑和测试过的。这是对单元测试的理解。单元测试应该只针对目标方法的业务逻辑测试,dao等服务应该在自己的单元测试中进行测试。对于依赖的第三方,我们应该相信他们会做我们期望的事情。这句话很难理解,不是吗?举几个例子例1:方法的最后是执行dao的create操作,那么如何验证呢?我们要验证的是:dao的create方法已经被调用并且调用次数正确,调用参数也正确。只要这三个验证通过,那么本案的执行就通过了。因为我们相信dao的create操作可以正确的完成我们所期望的,只要我们调用的次数正确,参数正确。在dao的单元测试中保证了dao执行的正确性。RemoteIntegrationTest中的第三步验证同理。我们要验证RPC接口是否被调用,次数和参数是否正确。那么我们的案子就通过了。至于RPC服务器是否正确执行,那是他们的事情,不是我们关心的。Mockito框架的验证接口就是这样做的。如果你理解了以上内容,那么你就会豁然开朗,UT也不会变得那么难写了。什么时候使用单元测试,什么时候使用集成测试?在我的实践中,我发现对于简单的业务,比如crud类型的瘦服务,更适合做集成测试。以下几种情况适合进行单元测试:Util类包含远程调用方法,输入较少,业务逻辑复杂的方法需要异常处理。案件详细到什么程度?这个问题也比较经典。如果一个方法覆盖了所有路径,那么需要写很多case,真的很累。我的建议是两个原则:1.核心逻辑,容易出错的逻辑一定要覆盖2.根据自己的时间。没必要写很多。毕竟机箱的维护成本非常高。一旦业务逻辑发生变化,案例也必须相应地改变。综上所述,本人目前从事开源项目(Apollo(配置中心))的研发工作。开源项目对代码质量的要求比较高,UT当然是很重要的一环。一开始不知道怎么写UT,当然态度上也不太重视UT。老板的代码UT覆盖率很高,他本着对开源负责的态度慢慢接受和学习了UT。尝了几次甜头后,他发现UT确实很实用,价值也很高,可惜UT被大多数开发忽视了。当然,我对UT的理解和实践还不够,还需要继续实践模型。最后想说:等功能开发好了,UT就做好了,放心上线,你的UT就成功了。近期热点文章推荐:1.1,000+Java面试题及答案(2021最新版)2.别在满屏的if/else中,试试策略模式,真的很好吃!!3.操!Java中xx≠null的新语法是什么?4、SpringBoot2.5发布,深色模式太炸了!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!