从零开始写单元测试( 二 )


什么不是单元测试?很多同学容易将其他测试与单元测试搞混,最常见的是会启动Spring上下文的集成测试 。比如使用@SpringBootTest注解可以启动Spring上下文,这可以测试依赖是否正常注入等Spring的功能,但运行一次需要耗费很多时间(因为要启动Spring上下文),也并不是真正的“单元测试”,因为它依赖了Spring框架 。
如何写单元测试那具体如何写单元测试呢?我们业界有一个叫做「TDD」(测试驱动开发)的方法论 。TDD的核心在于“驱动”二字,它的理念是从测试视角出发,通过测试驱动出来产品代码 。而在测试金字塔中,单元测试与开发人员最息息相关,所以这里的“测试”一般是指的单元测试 。
TDD大概分这几个步骤:

  1. 理清需求
  2. 设计类和方法的出参和入参
  3. 写测试代码
  4. 驱动出产品代码
  5. 重构,循环3-5步 。
首先要理清楚需求,因为只有理清楚了需求,才能保证我们使用TDD驱动出来的代码是跟业务期望的一致的 。然后第二步是设计类和方法的过程,也称为Task List 。这一步可以设计好类与类之间的关系,方法的出参和入参 。其实不使用TDD也会有前面这两个步骤,只不过使用TDD的话,可以帮助你更好地从业务视角出发,先把该设计的东西都设计好,避免直接上手写代码,写到一半的时候觉得不对,再去改 。
3-5步其实是一个循环的过程 。因为刚开始写代码可能并没有太注意代码的格式、风格、性能,一气呵成写得比较快,让测试通过 。等测试通过后,可以回过头来重构一下之前写的代码,重构后再跑一遍所有的单元测试,看是否有挂掉的单元测试,以此来检测重构是否对期望的输入输出有影响 。
单元测试的结构一个完整的单元测试,应该分为4个部分:
  1. 声明和参数
  2. 准备入参和mock
  3. 调用产品代码
  4. 验证,也叫断言
拿JAVA来说,单元测试框架有几个,最流行的应该是JUnit和TestNG 。笔者使用JUnit多一点,JUnit使用@Test注解在方法上来声明一个测试 。JUnit最新版本是JUnit 5,JUnit 5相较于上一个版本,在参数化测试方面做了很多改进,这样我们就不用写很多个高度相似的测试方法了 。
?
关于JUnit 5参数化测试,大家可以查看官方文档,也有对应的中文翻译,很方便阅读 。也可以去我的个人网站搜索《JUnit 5参数化测试》 。
?
一般来说,方法名需要尽可能可读,它可能比较长,但能够清晰地表述这个测试的意图,比如:
@Testvoid shouldReturn5WhenCalculateSumGiven2And3() {}@Testvoid should_return_5_when_calculate_sum_given_2_and_3() {}复制代码具体使用驼峰命名法还是下划线,根据自己团队的规范来就好,尽量所有测试风格保持一致 。(个人更喜欢下划线~)
入参一般是基本类型或者POJO对象,有些参数可以抽成变量,后面在验证阶段可能用得上 。
如果产品代码有外部依赖,就需要用mock来消除外部依赖 。常见的Mock框架有EasyMock、「Mockito」等,大家可以对比一下各个mock框架的区别,选择一个合适的 。
很多同学刚开始写单元测试的时候不能理解为什么需要mock,觉得mock比较麻烦,甚至有点多此一举的感觉 。其实不然,mock的意义在于,你「可以保证你的测试只测试了你要测的那部分代码」 。这样如果测试不通过,你就可以知道一定是要测的那个方法有问题,不可能是外部依赖的问题,这样才能做到真正的“单元”化,才能保证每个测试足够小,足够纯粹 。
准备好入参和mock后,会显式地调用一下要测的那个方法,这个一般只有简单的一行 。
最后是验证,验证分为好几种,最常用的是验证出参是符合自己期望的 。也有时候会验证异常等边界情况 。JUnit等测试框架基本上自己带了验证的功能,但API都比较简单,个人感觉不是特别好用,推荐使用「AssertJ」,功能强大,API用起来也比较舒服 。
举个例子吧:
@Testvoid shouldReturnUserWithOrgInfoWhenLoginWithUserId() {    String userId = "userId";    String orgId = "orgId";    User user = UserFactory.getUser(userId);    Org org = OrgFactory.getOrg(orgId);    given(orgService.getOrgById(orgId)).willReturn(org);        UserInfo userInfo = userService.login(userId);        assertEquals(org, userInfo.getOrg());}复制代码


推荐阅读