引子
当我第一篇博客发布,并被张善友老师的公众号转载之后,在公众号文章和博客园的留言中,许多开发者纷纷表示,单元测试作为企业行为,与实施的技术栈不同,不是开发者个人行为,实施单元测试花费的时间精力过于庞大,与实际效果严重不对等,而且如果过度的采用单元测试,也会增加新的测试点,因为单元测试代码本身就需要进行测试等。
从这些回复可以看出,程序员要不要编写单元测试这种话题,大概做传统开发时,问程序员要不要写项目文档一样充满争议。正如大家都深刻明白项目文档的重要性,但是一旦程序员需要编写项目文档了,往往会对这个事情产生抵触情绪,这实际上是绝大多数开发者的通病。
单元测试也是这样的矛盾纠结体。在长沙.net技术社区就有不少朋友纷纷表示,他们都曾经试图在公司推动单元测试的应用,但是受到了自上而下的反对声,最终迫于压力,只能放弃。 而之所以阻力这么大,其主要原因是编写单元测试会增加额外的时间,因为编写单元测试,不仅仅只是编写一个简单的测试入口,而是一系列步骤,但是领导要求在最短的时间看到效果,并且有的领导还经常改变主意,如果设计了一个优秀的单元测试用例,有时候甚至会因为无法适应需求的变化,而最终腐烂。
显然,使用单元测试和更高的单元测试的代码覆盖率,大概是一个试金石。过去在长沙还很少有企业会问开发者会不会使用单元测试,但是近年来越来越多的企业会问候选人代码覆盖率的问题,所以作为开发者,可以尝试从单元测试开始,努力提高自己的代码习惯,编写更加高质量的代码。
有哪些公司在要求编写单元测试?
过去,单元测试一直是专业软件公司的首选,有一位原诺基亚核心部门的开发者说为诺基亚内部,对单元测试的要求很高,虽然没有达到百分之百,但也有非常高的要求,在《构建之法》中,邹欣老师介绍了微软的开发实践,也对单元测试覆盖率提出了很高的要求。 当然有的读者或许会嗤之以鼻,这些都是古典软件公司,举这些例子有什么意义呢?中国式IT 公司,哪家都是996,哪里还有什么时间实行单元测试?
然而,优秀的互联网公司都开始推行devops 作为企业信息化建设过程中的最佳实践标准,持续集成和持续发布都对单元测试有很高的要求,例如在阿里巴巴java 开发者手册中,就明确提出了以下一系列指标,要求开发者务必采用单元测试方法,尽可能的提高代码质量。
【强制】好的单元测试必须遵守 AIR 原则。
...此处省略七百字
【推荐】单元测试的基本目标:语句覆盖率达到 70% ;核心模块的语句覆盖率和分支覆盖率都要达到 100%
参见原文阿里巴巴Java 开发者手册,由此可见,单元测试已经作为一种行之有效的手段,显然已经成为了中国优秀互联网企业的必然之选。
单元测试中的代码覆盖率有什么用?
代码覆盖率是单元测试的重要衡量指标,反映了单元测试中测试用例对被测代码的覆盖程度,是对代码的测试质量衡量的重要指标。
在十年前,博客园《代码覆盖率浅谈》(参考资料1)一文,深入浅出的介绍了单元测试的四种类型, 包括语句覆盖,判定覆盖,条件覆盖,路径覆盖四种类型,作者指出,单元测试覆盖率结果,有以下作用:
a. 覆盖率数据只能代表你测试过哪些代码,不能代表你是否测试好这些代码。
b. 不要过于相信覆盖率数据。
c. 不要只拿语句覆盖率(行覆盖率)来考核你的测试人员。
d. 路径覆盖率 > 判定覆盖 > 语句覆盖
e. 测试人员不能盲目追求代码覆盖率,而应该想办法设计更多更好的案例,哪怕多设计出来的案例对覆盖率一点影响也没有。
在软件开发过程中盲目的追求的高代码覆盖率,往往得不偿失,尤其是为了提高代码覆盖率而做的单元测试,往往只会成为累赘。
合理的操作形式应该是基于实际用例出发,设定更多的用例场景,实现基于用户场景驱动的单元测试覆盖,像在阿里巴巴开发者手册中说的,测试人员与开发人员配合,共同完成测试用例覆盖,就是一种不错的应用实践。
当然,即便测试缺位,开发者也完全应该主动的承担更多单元测试的职能,尽可能多的思考用户场景中可能存在的变数。
编写好的单元测试的一些小技巧
技巧一,多看书肯定是不错的
有朋友问,怎么写好单元测试?有什么书推荐么?我很惭愧,我自己的代码单元测试的覆盖率还相当低,可能没办法给出指导,我想多看书肯定是没错的,而编写单元测试的书,还挺多的,例如这一本,《单元测试的艺术》,一看就是基于C#的,可以试一试。
而想入单元测试的门,可以看看我后面找到的一系列引文,相信能给你带来方便。
技巧二,运用测试框架
1、单元测试框架:XUnit、NUnit、MSTest等.
2、测试运行工具:xunit.runner.visualstudio 。类似如:Resharper的xUnit runner插件。
3、模拟框架:Moq、RhinoMocks、NSubstitute、FakeItEasy等。
技巧三,灵活的运用事务回滚或内存数据库,避免单元测试数据污染正常数据
前者是阿里巴巴开发者手册中提到的一种方法,在有的场景下也挺实用的,不过有开发者指出,可以使用模拟内存数据库来解决这个问题更为妥当,例如使用Effort.EF6,通过nuget获取,使得创建一个伪造的、供EF容易使用的内存数据库成为可能。与这类似的,还可以使用HttpSimulator来模拟http 请求。
技巧四,使用依赖注入和单例模式改良不可测代码
静态类为代码编写带来了许多便利,但是也使得代码测试变得相对困难,而使用单例模式进行改良则使得操作更可控。
总结
人生苦短,撸码不易。从选择成为开发者的那一天起,我们就被迫承受了许多压力,尤其是技术发展的不确定性,更是如此,你永远也不知道自己当下的选择是否正确,说不定你今天最为熟悉的技术或框架,明天就凉凉了。尤其是现在的各种自媒体,时不时的发几篇文章来输出焦虑,巴不得天天说优胜劣汰才能获得读者的关注一般,让开发者们压力更大。
我觉得,技术是解决问题的方法,而良好的代码习惯则是自身心法,尤其是单元测试,更是一种好习惯,先别总想着担心自己被淘汰,努力的使自己习惯更好,总会获得无穷收获。
参考资料1:《代码覆盖率浅谈》https://www.cnblogs.com/coderzh/archive/2009/03/29/1424344.html?tt_from=copy_link&utm_source=copy_link&utm_medium=toutiao_ios&utm_campaign=client_share.
参考资料2:《代码覆盖率强迫症》https://www.infoq.cn/article/test-coverage-ocd.
参考资料3:《代码覆盖率 (Code Coverage)从简到繁》
https://www.cnblogs.com/jacksundatashare/p/5083352.html.
参考资料4,《C#单元测试,带你快速入门》http://www.cnblogs.com/zhaopei/p/UnitTesting.html?from=singlemessage&isappinstalled=0.
码字不易啊..哈哈
(本文是作者为社区贡献的内容,赞赏的所有费用都将作为长沙.NET技术社区运营费用,感谢您的关注,让我们一起努力,共同创造长沙.NET技术社区更好的明天)