作者|问元为什么需要单元测试纵观优秀的开源项目,完整的单元测试总是必不可少的。通过这些单元测试,我们可以充分了解代码中相关类和方法的功能和核心逻辑,熟悉各种场景的操作。同时,由于进行了单元测试,开源作者在接受各种特性的代码提交时可以保证稳定性和安全性。其实所有开发同学都应该知道单元测试的重要性。同样,TDD(TestDrivenDevelopment)并不是一个新概念,但是当我们将其付诸实践时,总会发现各种原因。说服自己下次好好写单元测试,这次就放手吧。这些原因无非是开发周期太紧;测试学生可以保证函数的正确性;编写单元测试代码比业务代码大;跑起来也不是不可能。所以虽然我们一直在追逐工程师文化,但时不时也会沉溺于放弃工程师背景的路上。单元测试是项目交付前质量保证的第一个环节,无疑是软件工程质量保证的重要基石。有效的单元测试可以提前发现90%以上的代码错误,同时防止代码损坏。结构演进中起着至关重要的作用。如何写单元测试好的单元测试的几个要点摘自阿里巴巴开发规范:单元测试必须遵守AIR原则,单元测试必须具备Automatic(自动化)、Independent(独立)、Repeatable(可重复);单元测试应该是全自动和非交互式的。测试用例通常定期执行,执行过程必须完全自动化才有意义。输出需要人工检查的测试不是好的单元测试;单元测试应该保证测试粒度足够小。单元测试测试粒度足够小以帮助查明问题。单元测试粒度最多是类级别,一般是方法级别;单元测试必须遵守BCDE原则,边界、边界值测试,包括循环边界、特殊值、特殊时间点、数据顺序等;正确,正确的输入,得到预期的结果;设计,结合设计文档编写单元测试;Error,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),得到预期结果;核心业务、核心应用、核心模块的增量代码必须保证单元测试通过;这里的单元测试编码范式主要使用Mockito单元测试框架作为模板1.Mock:通过mocks如when().thenReturn/thenAnswer/thenThrow或doReturn().when()方法将模拟依赖类方法到模拟服务依赖或中间结果。2.DO:调用被测类的方法,执行测试链接。3.Verify:验证执行结果的正确性,通过Assert验证数据结果的准确性,通过Verify验证链接是否正确执行,通过expected=Exception.class验证异常链接。publicclassTest{//0.依赖类@MockDependencyClassdependencyClass;//0.待测类@InjectMocksTestClasstestClass;@BeforepublicvoidsetUp(){MockitoAnnotations.initMocks(this);}@TestpublicvoidtestMethod(){//1.mock,依赖方法,构造中间层数据when(dependencyClass.someMehod(any())).thenReturn(mockData());//2.做,调用被测类Resultresult=testClass.testMehod();//3.验证,验证结果数据Assert.assertEquals("someexpectedresultstring",result.getModel());}}当然,虽然写单元测试用例比较模板化,但我们也需要充分利用单元测试框架(Junit/Mockito/PowerMock/Spock),掌握一些技巧才能写得快、准和无情的单元测试用例,这也是研发同学必须掌握的基本功。单元测试编码效率IDEA上有很多单元测试插件,可以半自动生成单元测试类文件。这里推荐使用TestMe插件。TestMe插件可以智能分析被测类的依赖类,结合Mockito+Junit等单元测试框架,生成Mock/InjectMocks依赖,自动生成单元测试类。假设业务代码如下:@ComponentpublicclassDefaultMemberManagerimplementsMemberManager{@AutowiredprivateMemberDAOmemberDAO;@AutowiredprivateCacheManager缓存管理器;@OverridepublicDatequeryActivationTime(longuserId){DateactivationTime=cacheManager.getTimeif==niuseractivationTimeif(Id);){MemberDOmemberDO=memberDAO.queryByUserId(userId);如果(memberDO!=null){cacheManager.saveActivationTime(userId,memberDO.getActiveTime());activationTime=memberDO.getActiveTime();}}返回激活时间;}}然后通过TestMe快捷键COMMOND+N,可以自动生成下面的单元测试类publicclassDefaultMemberManagerTest{@MockMemberDAOmemberDAO;@MockCacheManager缓存管理器;@InjectMocksDefaultMemberManagerdefaultMemberManager;@BeforepublicvoidsetUp(){MockitoAnnotations.init;Mocks(this)}@TestpublicvoidtestQueryActivationTime()throwsException{when(memberDAO.queryByUserId(anyLong())).thenReturn(null);when(cacheManager.getActivationTime(anyLong())).thenReturn(newGregorianCalendar(2022,Calendar.MARCH,5,23,2).getTime());日期结果=defaultMemberManager.queryActivationTime(0L);Assert.assertEquals(newGregorianCalendar(2022,Calendar.MARCH,5,23,2).getTime(),结果);}}TeamUnitTest构建覆盖率概念覆盖率是在单元测试命令运行时,通过javaagent挂载类JaCoCo的插件进行代码覆盖检测的方式,计算单元测试执行过程中代码覆盖的比例,生成一个通用的coverage指标为覆盖率,又可以细分为语句覆盖率、条件覆盖率、分支覆盖率、路径覆盖率等。这里目前比较关注的是语句覆盖率和分支覆盖率,尤其是增量代码覆盖率,更能体现更改代码的单元测试覆盖率。如何进行单元测试这里我们使用阿里研发平台Aone的测试实验室功能,Aone实验室支持测试任务插件的排列组合,通过独立的测试资源来执行测试任务。所以我们对代码拉取插件、单元测试插件和覆盖率计算插件进行整理配置,形成最终的执行流程:拉取代码;执行单元测试命令;分析单元测试结果;计算覆盖率。最后完成整个项目的单元测试覆盖率计算。单元测试覆盖率结果示例如下:什么时候触发单元测试?单元测试任务主要通过持续交付流水线进行集成。目前主要的触发策略有以下几种。代码分支符合单元测试标准在发布过程中,确保最终集成发布的所有分支代码都符合单元测试标准。代码行覆盖率为85%代码规范扫描增量问题总数为0团队测试覆盖率。综上所述,到2022年,当前团队各应用(边缘应用除外)单元测试增量代码覆盖率达到85%标准,最新平均增量代码行覆盖率达到88%,整体全代码覆盖率达到85%。平均增长了20%%。诚然,提高单元测试覆盖率并不是最终目标。覆盖率高并不能完全代表项目质量高,但没有单元测试或单元测试覆盖率低的项目,代码质量和稳定性一定不高。同时,团队中的研发同学也对单元测试有了新的认识,自测和测试质量明显提升。全年无因代码质量问题上线,有效提升了项目质量和服务稳定性。后续规划,持续优化单元测试质量,提升分支覆盖,优化边界异常覆盖;关注单元测试编码效率的提升,优化测试用例与测试数据的分离;关注核心环节单元测试覆盖率;巧妙地将TDD思维运用到业务开发过程中。
