关于单元测试的几点思考

    单元测试不是什么新鲜概念,随便问一个程序员,TA都能告诉你单元测试是什么,甚至告诉你具体地使用某个语言如何写出单元测试的测试用例。

在最近的一个项目中,本人所在的团队践行单元测试的实践,却发现同事们对于单元测试有很多不同的见解,甚至是误解。Unit Testing虽然不是什么高精尖,

但是它几乎是每个开发者每天都要用到的武器,所以还是值得思考和讨论的。

 

    先来一点关于这个项目的背景介绍,以便于讨论。之前有一个项目,姑且称为v1吧,是国外团队开发的,几年前移交给宇宙中心的团队。

这是一个典型的Python开发的web项目,核心部分是REST service。之前国外的团队并没有特别多的Python经验,多数都有C++经验。所以别指望这个v1的代码能够是优美的Pythonic了。而且因为Python这门语言很灵活很强大,(划重点啦,任何一门灵活强大的语言都不能阻止程序员写出烂代码!)再加上动态语言的特点,代码很多地方的封装性很差,模块内部和模块间的各种耦合非常厉害。这样的代码难以理解,难以扩展,可测试性非常差。没有足够单元测试的保证,谁也不敢轻易重构。(关于动态一时爽,重构火葬场的吐槽,来日再谈)

    去年由于某个契机,团队开始在v1的基础上开发v2,并且可以进行非常大的架构调整。v2大部分都是全新的代码,团队吸取v1中的教训决定要从一开始就严格把控代码质量。一个决定就是单元测试(Unit Testing)要覆盖100%的代码。v2是用Python 3.6开发的,单元测试使用的是pytest。项目开发一段时间后,发现一些问题或者分歧。

 

壹:不理解单元测试的概念

这个团队其实已经足够senior,成员基本上都是工作10年以上的。但是说起单元测试的概念,也有人不是非常理解。

主要问题是没有理解单元测试的“隔离性”,也就是说单元测试不能依赖外部环境,例如网络,外部数据库,第三方的web service等等。如果你发现单元测试的代码中有使用到网络I/O,数据库连接或者调用外部的服务如AWS services等等,这样的单元测试就不是正宗的unit tests, 而应该算是集成测试了。对于所有的“环境”相关的东西,都应该mock掉,这样保证单元测试的代码在没有网络,没有数据库和外部系统的情况下都能运行。想象一下,给你一台电脑,把你的代码git clone了,配置好开发环境,这时候单元测试就能够跑起来了,哪怕是把你的电脑丢在火星上。

另一个问题是对于“单元”二字的理解。每个编程语言中的基础block不一样,就Python而言,单元测试是对class和function的测试,所以说Python单元测试的“单元”就是class和function。你可能会问“那么模块和包呢”,模块和包都是由class和function组成啊,应该测试这些。而不是将package/module作为一个unit, 因为那样的unit太大了。Python中跟其他语言不一样的地方是package的初始化,也就是__init__.py文件。我见到一些项目在__init__.py中做很复杂的逻辑,其实这是不推荐的,我个人认为也是不利于engineering的做法。如果这里边有复杂的逻辑,影响也不大,如果组织成了一个个职责分明的函数,也是容易做单元测试的。

 

贰:单元测试只测试接口

刚刚毕业的时候在某个团队做实习,主管分配的一项任务就是给一个美国同事的代码写单元测试(聪明的读者在这里看到了另外一个错误,对不对?)。我压根就不知道单元测试是什么,主管说你就把所有的公有接口都测一遍吧。当时的项目是C++写的,的确没有太多方法来测试私有函数。换个角度看问题 - 其实如果代码写的足够好,通过公有接口完全能够测试所有的私有函数并且达到很高的覆盖率。但是,如果技术上可行,非公有的部分也应该考虑进来。

 

叁:单元测试要做到覆盖率100%

对于单元测试要做到覆盖100%代码这个观点,我持保留态度。尽管我目前的项目做到了代码行100%的覆盖率,但是我不认为这是个值得做的事情。因为这太花时间精力了,投入产出比并不高。写过单元测试的朋友们知道,如果要做到80%甚至90%的覆盖率,努力点不难。但是要做到95%以上是非常困难的,而且是越来越难的。就跟攀登珠峰一样,前面的8000米可能都没多大问题,但是最后的200-300米简直是举步维艰。常常出现的情况是,代码实现可以一炷香的时间就搞定了,但是要写完所有的test cases,需要小半天甚至更久。“边际效用递减”的问题在这里也很明显:做到95%的覆盖率对于大多数项目来说已经足够了,为了最后的5%花费特别大的努力,值得吗?许多著名的开源项目也没有要求做到覆盖率100%,但是并不影响它们的伟大。

这里的讨论也关系到“程序正确性”如何保证的问题,稍后我们继续讨论。

各位读者,对于自己团队或项目的覆盖率要求是多少呢?是根据什么来设定的呢?欢迎留言交流!

 

肆:单元测试做到覆盖率100%就能够保证程序绝对正确?!

领导拍拍脑袋就决定的事情,通常都是个大坑。-- Mr. Unknown

覆盖率达到100%这一点对于人的直觉有很强的冲击力 - 大脑可能放弃思考了:全部覆盖了,代码逻辑肯定是正确的。

错!程序只是程序员按照自己对业务的理解来做的,如果理解本身就是错误的,那么测试代码也是按照错误理解来编写的。- 毕竟,单元测试是白盒测试。写单元测试,首先就是理解代码的逻辑,理解每个单元的输入输出和处理过程。另外一点,如果测试的是系统的边界类(不限于class),单元测试对于外部系统的理解可能不正确,导致mock的部分跟真实情况不一致。所以,希望单元测试能够代替其他所有类型测试的同学,可以醒一醒了。

 

伍:每个test case只能有一个断言(assertion)

记得读过一本书(不确定是不是《单元测试的艺术》),书里提倡单元测试的每个case有且仅有一个断言(assertion)。对于这一点,我并不认同。例如测试某个函数的返回值,这个值是个复杂对象,不一定能够用一个简单清晰的表达式来检查。但是,目标函数或类应该遵守SRP,这样单元测试就容易做到简单明了。


以上所讲的是个人的理解和感受,不一定正确。还请各位朋友指正。

 

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bruce Jia(上海)

熬夜码字换酒钱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值