单元测试
单元测试的意义
单测好处:
- 单元测试使工作完成的更轻松
- 单元测试使你的设计更好
- 大大减少花在调试上的时间
- 能帮助你更好的理解代码
单元测试是什么?
指对软件中最小的可测试单元进行检查和验证,调用被测服务的类或方法,根据类或方法的参数,传入相应的数据,得到一个返回结果,最终断言返回的结果是否符合预期。如果相等,测试通过;如果不相等,测试失败。
所以,单元测试关注的是代码的实现与逻辑。单元测试是最基本的测试,也是测试中的最小单元,它的对象是函数对象,也可以包含输入输出,针对的是函数功能或者函数内部的代码逻辑,并不包含业务逻辑。
该类测试一般由研发人员完成,需要借助单元测试框架,如java的Junit、TestNG,mockito,python的unittest等
好的单元测试准则
1.运行快速
单元测试运行比较频繁,如果打包时候,单元测试运行很慢,会很影响效率。
- 单个测试小于200ms
- 单个测试套件小于10s
- 整个测试小于10分钟
2.一致性
任何时候,同样的输入需要同样的结果。
3.原子性
所有的测试只有两种结果:通过和未通过。不能存在部分测试通过的情况
4.单一职责
一个测试只验证一个行为。
- 一个方法,多个行为->多个测试
- 一个行为,多个方法->一个测试(一个行为,多个方法一般指该方法调用private,protected,getters,setters)
- 多个assert只有在测试一个行为时可以接受
5.独立无耦合
单元测试之间无相互调用
- 单元测试执行顺序无关
- 不同顺序无影响
单元测试之间不能共享状态
- 比如不能共享变量,如果需要,放在setup里
6.隔离外部调用
- 单元测试需要快速运行,且每次结果一致,所以需要隔离一切对外部的调用
- 不使用具体的其它真实类(就是不要new)
- 不读数据库
- 不读网络
- 不读外部文件
- 适当时候可构建相同的内部文件mock
- 不依赖本地时间
- 不依赖环境变量
7.自描述
- 单元测试是开发级文档
- 单元测试是方法的描述
8.单元测试逻辑
- 单元测试必须容易读和理解
- 变量名,方法名,类名
- 无条件语句,无swith(分解if到多个测试,所有的输入都是已知的,所有的结果都是一定的,可以mock)
- 无循环语句
- 无异常捕捉(测试预知的异常,用ExpectedException方法)
9.产品代码
- 产品代码不能有测试逻辑
- 测试代码和产品代码要分离
- 使用依赖注入
- 不要在产品代码里有任何只供测试的代码
根据上述指导思想和实际实现情况,一般在实现单元测试时有两种不同的实现方式
- 单层隔离
- 内部穿透
单层隔离
正常代码分层会分为controller、service、dao等,在单层隔离的思想中,是针对每一层的代码做各自的单元测试,不向下穿透。这样的写法主要是保证单层的业务逻辑固化且正确。
实践过程中,例如针对controller层编写的单元测试需要将对应controller类代码文件外部所有的调用全部mock,包括对应的内部/外部的service。其他层的代码也是如此。(可以参考样例代码中cdo-test-sample-core层的单测代码)
好处
- 单元测试代码极其轻量,运行速度快
- 真正符合了单元测试的原则,可以在断网的情况下进行运行,屏蔽服务注册和配置管理,各种中间件的影响
- 单元测试质量更高
缺点
- 单元测试的代码量比较大
- 对于低复杂度的项目比较不友好(例如项目是单纯分层之后的CRUD)
内部穿透(集成测试)
穿透,自然就是从顶层一直调用到底层,为什么还要加上内部二字?就是除了项目内的方法可以穿透,项目外部依赖还是要mock掉。实践过程中,就是单元测试针对controller层编写,但会完整调用service、dao,最终对落地结果进行验证。(可以参考样例代码中cdo-test-sample-api层的单测代码)
好处
- 代码量相对较小
- 学习曲线低,穿透的单元测试更偏向黑盒,开发人员构造输入条件,然后从落地结果中验证预期结果
缺点
- 整体较重,启动spring容器,中间件mock,整体单元测试运行预计需要分钟级别。所以基本是要在ci的时候来执行
这两种方法,具体使用哪种合适,应该由项目成员根据项目实际情况自行选择,一般建议核心项目两种执行方法都可以同时执行,具体实施时可以先执行“单层隔离”积累单测用例,再做“内部穿透”。
代码语言:txt复制 PS:我们一般使用@SpringBootTest注解进行集成测试,使用其它spring test(@WebMvcTest)注解进行特定组件的单元测试。
参考文档:
- https://howtodoinjava.com/spring-boot2/testing/testing-support/