在测试上难以自动化的软件,很难成为好的软件。 – 《Google软件测试之道》
对于单元测试的想法
对于单元测试,开发者总是有着非常矛盾的思想。
- 单元测试很好,它能帮助我们找到很多隐藏的bug
- 但写单元测试,比搬砖还累,我真的不想写~
没错,单元确实是一个磨炼意志的东西,如果不是在大公司,人力也不够,而由于单元测试往往是没有 KPI 的,所以经常在做完功能测试之后就快速上线不断迭代了。
但,如果想要成为一个出色的软件,或者说是作品,单元测试是保证可持续发展的地基。比如,很多的开源软件,为了保证可靠,单元测试覆盖率往往都有着很高的标准。
于是,从 JAVA 的 JUnitTest 的坑爬出来之后,我准备来写写,在 Golang 中我们如何做单元测试。不一定是最佳实践,但绝对算是不错的参考。本文先来说说单元测试的要求和注意点。
什么是单元测试
笔者从书中总结了一句话:自动化验证一个独立模块的代码是否能满足预期要求的测试。
所以单元测试是一个最最底层的一个测试,思路就是保证最小的模块没有问题,只有底子稳了,上面的业务才不容易出现问题。
要求
总结自《代码整洁之道》
自动化
自动化是一个最基础的要求,你不能说单元测试是通过你手动点击某个按钮,输入一个用户名密码,这样进行测试。单元测试应该就是启动之后自动完成的,并且对于测试结果应该做到自动化验证,而不是通过人的眼睛去判断输出的内容是否符合产品需求。
快速
单元测试应该测试的够快,如果单元测试跑的太慢会导致的一个问题就是人们很少去运行它。如果一个单元测试要跑 10 秒钟,那么开发者就会想着反正 10 秒钟,跑就跑一下。频繁的跑单元测试更容易提前发现未知的问题。
环境一致
测试的环境应该是和使用环境一致的,这也是很多测试的一个基本保证。使用环境是 mysql8 你用 mysql5.7 进行测试就会可能遗漏问题。并且这个测试环境应该是包括在单元测试里面的。
独立
单元测试应该是独立存在的,也就是对于执行顺序应该是没有要求的。A 模块先测试然后 B 模块再测试就没有问题;但是 B 模块先测试 A 模块再测试就会报错,这样是不行的。这也是一个最基本的要求,一个单元测试运行的前后,要么数据现场恢复,要么你能保证当前的测试数据一定不会影响后面的测试。在你通常无法保证的时候,原则上都要进行数据恢复。比如你测试增加1条数据,那么测试完成之后就需要将数据删除。
注意事项
不要为了追求覆盖率而忘本
拿数据说话,在很多地方都适用,数据往往能给人们很大的安全感,没错,单元测试覆盖率 99% 那么可以给人非常大的安全感。但是!这是不太现实的。 对于绝大多数的软件来说,只要软件项目够大,你要支持的覆盖率越大,你的开发成本也就越大。
最重要的是:即使你的代码覆盖率是 100% 也不是情况 100% 覆盖。代码虽然这部分跑过了,但是由于数据的不同,一个边界条件就能让你相同的代码报错。打算法比赛的人都知道,同样的功能,只有测试数据全过才能 AC。所以与其去测试一个非常偶然的数据库连接异常,不如多测一组 ‘-1’ 数据会不会报错。
单元测试既有开发成本也有维护成本
《单元测试的艺术》中说到单元测试的成本:
一般完整的单元测试和开发成本是一致的(开发一天;测试一天,这件事其实很难衡量性价比)。而且单元测试写的越多,维护成本也就开始不断增加,当项目的功能不断变化,单元测试也要跟着调整,也就是说,你的敏捷开发和快速迭代会被单元测试所拖累,你需要衡量成本。
并不推荐一开始就写
在国内(拥抱变化),特别是中小型公司,往往业务功能是不稳定的,需求一直在改变,所以其实我并不推荐一开始就写单元测试。有时间不如将功能测试全部通过,这样能更快上线,更快有反馈。尤其是像国内这种以产品为导向的开发方式,需求变动往往只需要一个小时,前一秒是产品经理认为可以,下一秒老板就说不行的情况太多了。在业务稳定之后,慢慢补,我个人真的觉得并不是一件错事。 当然,大公司为了保证业务可靠,也有测试左移等等思路,并且一些有着完整规划并且实施方案的项目,TDD(测试驱动开发)也未尝不可,总之也是需要具体情况具体分析的。
PS:我也曾经有一个项目体会过测试驱动开发,写了一大堆测试用例和单元测试之后,当实际在开发功能的时候还再回过头来修改测试用例