可能大家会觉得,单元测试有什么好讲的,那请大家思考以下问题:
- 你有没有写过单元测试
- 你写过多少单元测试,是否感到厌烦
- 当提到单元测试,你的第一反应是什么?只是写一段代码来测试代码?有没有一套完整的原则去遵循?
- 当你写了一段代码来测试,是否只是测试了正确输入,得到正确结果?
- 你的单元测试运行过多少次? 是否只是第一次运行了就再也没管过,尽管后来代码改了?
如果上面有哪一条满足了,我觉得就有必要研究下单元测试。
单元测试是什么
必须先形成一种思想:是发自内心的,而不是被强迫去做,成为一种负担。不要为了写单元测试而写单元测试!
谨记:单元测试不是负担,而是一种资产,能够为我们带来价值和便利。
价值:
- 深入理解需求
- 覆盖边界条件,减少bug
- 工作量证明,有据可寻,出现问题易于排查,心理上的安慰
便利:
- 修改代码后可以快速判断对不对(infinitetest)
- 从测试理解函数的作用,对别人有帮助
缺点:
- 工作量变大了:体现在2个方面
- 写的多了
- 改得多了,如果原来的业务变了,则该删的和该修改的代码也多了
- 对程序员的要求高了:如果是TDD,则需要先写测试,而很多人都是在写完业务才理解业务逻辑的
- 单元测试不能应对所有情况,有可能单个方法是对的,但集成在一起就出问题了
总的来说:利大于弊,单元测试能让未来更加美好!
单元测试的粒度
面向过程和面向对象以模块和类区分文件,但单元测试的粒度建议以方法(函数)来写。
理论上:根据单一性原则,一个类只完成一个业务,一个方法只完成一个功能,但现实是很残酷的,不是所有人都这么做,也不是所有功能的界限都那么明确。
比如登录功能:写在一个方法,但其实可以划分得更细:参数检测,用户名密码查询,返回结果。
我们采用一个方法一个单元测试:但一个单元测试并不是只有一个方法:
如果一个方法很复杂,参数太多,一个测试方法可能很长,可以适当地拆分,但只有一个方法有@Test
注解:
1 |
这样的话,拆分出的方法为了满足开闭原则,应该是private的,这给测试带来了了困扰。
//TODO
试试看反射是否满足要求
单元测试原则
每次谈原则之类地东西都觉得是虚的,太过于理论,我开始也这么觉得,但后来逐渐明白,原则,或者说原理是经过高度总结概括和抽象地,
如果看不懂原则,那说明你还处在社会底层,不能理解上流社会的简洁与美丽。
我可以举个简单的例子来说明这个过程如何达到:小时候我们肯定听过一句话:书读百遍,其意自现。
当然,背诵不是唯一的途径,主要是当提到单元测试时,内心第一反应就是这些原则,我们所坚信的东西。可以在每次写测试前后对代码进行原则比较,
看是否满足。
FIRST
- Fast:除去被测方法本身,单元测试不应该过于复杂,保证速度快,如果方法本身很慢就没办法了
- Isolated:隔离性,单元测试间不应该相互依赖,且没有先后顺序
- Repeatable:可重复执行,而不是随时间变化而不可预测
- Self-validating:可以自我验证结果,不需要人工干预,也就是使用Assert,而不是打印出来判断
- Timely:及时性,你肯定不会等代码上线了才来补测试把,能尽快就尽快
AIR
阿里的空气原则,来自Java开发手册:
- Automatic:自动化,类似Self-validating
- Independent:独立性,等于隔离性
- Repeatable:可重复性
测试的最佳实践
BCDE原则
阿里手册里提到的关于测试的范围的原则:
- B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
- C:Correct,正确的输入,并得到预期的结果。
- D:Design,与设计文档相结合,来编写单元测试。
- E:Error,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得
到预期的结果。
命名清晰
关于测试方法的命名不需要节省字符,尽量把做什么和期待什么结果写出来,这样注释就可以省了:比如:
1 | testCreateUserCorrect() |
测试代码与业务代码分离
对异常的捕获
1 |
别依赖于你的机器
单元测试里不应该包含只有你的电脑上才有的东西,比如一个PATH路径,一个文件之类的。
DAO层如何测试
Service层如何测试
使用spring Mock
我们的测试规范
Junit
1 |