【实践经验】单元测试怎么写
之前一段时间,在尝试完善项目的单元测试,做起来才发现,其实自己对于单元测试该怎么写一直没有特别清晰的认识,因此查了一些资料, 查的过程中发现,其实每个人的看法都不尽相同。每阅读一篇文章,都是对自己认知的一次更新。本文就是结合前人的看法,和自己的实践经验,总结出一些我自己当前认知状态下认为正确的东西,分享给大家。估计也不会是最正确的,也欢迎大家留言讨论。
先说一下我们写单元测试的目的,这个是下边所有讨论的基础。单元测试的意义应当在于保障修改代码的时候,保障存量代码的正确性。也就是说,修改代码后,执行一遍已有的测试用例,能执行的话就代表之前的逻辑依然是可用的。
下面就具体列一下个人认为写单元测试应当遵守的准侧。
避免对环境的依赖
单元测试不应当依赖于环境,包括对网络、db、io、数据、外部接口等的依赖。这一点应该争议不大,因为单元测试会有很多的执行场景,比如开发人员手动触发、提交代码时自动触发、打包时自动触发等,如果单元测试不能解除对环境的依赖,它的执行场景就会大大受限。
实现这一点,需要借助很多工具,例如内存数据库、redis的内嵌server, mock等待,势必会给写单元测试这个过程增加很多工作量,但是是无法避免的。
针对业务逻辑测试
测试应当是针对业务逻辑,而不是实现逻辑。这一点是争议会比较多的一点,主要是大家对于“单元”这个词的定义无法统一。有人认为一个函数就是一个“单元”,但是如果按这个标准去做单元测试,单元测试是达不到预期效果的。
举个简单的例子,代码重构的时候,要不要改单元测试?答案当然是不能,如果每次代码重构都要对应修改单元测试的话,单元测试的作用可以说就失去了一大半。所以说测试的一个“单元”应当是一个业务上的单元。当然这一点也不能太死板,比如你写了一个很复杂的方法,针对这个方法写一个单元测试也完全没有问题。这一点其实可以换个更准确的方法:针对意图写单元测试,而不是针对实现。也就是说,把代码的实现逻辑当做一个黑盒,只针对输入输出做测试。
去mock化
前边第一点里,我们提到过可以利用mock避免对外部环境的依赖。但是一定不能滥用mock, 可以阅读一下mock七宗罪 , 一个原则就是能不mock的时候就不要mock。什么时候是必须要mock的呢?就是前边说的要依赖外部环境的时候。除了这种情况,都不要mock.
单元测试倒逼重构
在写单元测试的时候,会发现有些测试会非常难写,而具体分析难写的原因,会发现有一些是因为代码本身写的不好。比如,一个类的依赖特别多,可能就是因为这个类的职责不明确,将不相关的代码放到了一起。这时候,就应该直接将代码重构,拆分成几个类,写单元测试的难度也会随之降低。