摘要:单元测试应该是程序员必备的技能,真正的编程高手应该善于把握单元测试的粒度。在之前的博客中提到,我最近重构了后端Node.js服务的代码,将Promise替换为Async/Await。这是一件痛并快乐着的事情。当任务完成50%时,我发现与其说重构,不如说重写更准确。一方面,切换到Async/Await本身就意味着每一个异步函数都需要修改,而且大部分后端函数都是异步的;另一方面,作为一个有强迫症的完美主义者,我写了很多单元测试,对代码做了一系列的优化,修复了一些bug,实现了一个新的功能。这里的关键词是单元测试,那么问题来了,重构代码后,应该写什么样的单元测试呢?不就是找麻烦吗?你必须知道,单元测试似乎比功能代码更难编写。这是一个有趣的话题。什么是单元测试在《玩转Node.js单元测试》中,我是这样定义单元测试的:所谓单元测试就是验证某个功能或API的正确性。这样的定义很好理解,但不是很准确,严格来说应该是错误的。因为API测试涉及多个功能,往往依赖于数据库、缓存、第三方服务等外部资源。因此,API测试应该属于集成测试而不是单元测试。根据《JavaScript有这几种测试分类》,集成测试与单元测试应该这样区分:单元测试是指测试小块代码,通常指孤立地测试单个功能。如果一个测试依赖于一些外部资源,比如网络或数据库,那么它就不是一个单元测试。集成测试就是测试应用程序中的不同模块如何集成和协同工作,这与其名字是一致的。集成测试类似于单元测试,但也有很大的不同:单元测试测试每个独立的模块,而集成测试恰恰相反。例如,当测试需要访问数据库的代码时,单元测试实际上不会访问数据库,但集成测试会。因此,对于单元测试,更准确的理解应该是独立测试单个功能。但是在实践中,在测试单个功能时,很难保证所谓的独立测试。有些功能不可避免地依赖于外部资源,例如其他功能、数据库、功能和第三方服务。这是我们很难避免的,有时甚至需要验证这些外部资源。例如,验证写入数据库或缓存的数据是否符合预期;验证数据库或缓存中的数据对功能行为的影响是否符合预期。在我看来,对单个功能的非独立测试也可以看作是“单元测试”。简单来说,本文所讨论的单元测试就是测试单个功能。重构和单元测试新功能的加入,代码复杂度的增加,需要优化代码,或者新技术的出现,都会导致需要重构代码。不写单元测试就对代码进行大规模修改是不可想象的,因为写错的概率太大了。我一直鼓励大家写单元测试,但是,有时候偷懒是难免的。准备重构代码的时候,发现自己写的单元测试不够用,尴尬:(那我是直接改代码;还是先写单元测试,再改代码?这个是一个艰难的决定,因为前者很难保证正确性,而后者似乎要花很多时间。有一种智慧叫做“摸着石头过河”:我试着写一些单元测试在修改功能代码之前。这个过程并没有想象中那么痛苦,也许是因为做决定其实比做事更痛苦,或者是因为我更喜欢敲代码。所以,我可以开始大刀阔斧的重构:切换到Async/等待;优化代码组织;优化程序性能;写新功能……我很忙。如果我不写单元测试,我敢做吗?当然不敢!如果我错了,我必须改吧。如果我不写单元测试,我会改的。这么快吗?当然不是!大概每次修改一个功能,我都会想很久,然后祈祷不要出错;修改函数不是一次性的事情。我还在敲代码而不是写博客。正是因为有了单元测试的保证,才会更容易改,效率也会更高。这样,既能保证正确性,又能节省时间。我想单元测试会浪费很多时间。时间少了,其实好像不是这样。Fundebug是一个全栈的JavaScript错误监控平台,支持多种前后端框架,可以帮助您第一时间发现bug!单元测试的好处可能是大部分人不像我一样喜欢折腾。我不想一直重构代码。这样的话,我不需要写单元测试吗?我想答案应该是否定的。因为单元测试有很多明显的好处:验证代码的正确性,验证边界条件,避免BUG重现避免修改代码时出错避免其他团队成员修改代码时出错,便于自动化测试和部署此外,单元测试可以提供另一种代码思维的视角,对于写出高质量的代码很有帮助。本文讨论的单元测试是针对每一个功能,那么大家在写单元测试的时候,会考虑合理的拆分和合并功能。因为如果函数的功能没有分清楚,单元测试就不好写。在敲代码的时候,我们考虑的是函数实现,不管发生什么,一写就完事了。在写测试的时候,我们跳出函数,从输入输出的角度去思考函数的作用。这时候你就会想,这个功能真的需要吗?这个函数的功能可以简化吗?这个功能好像不够全面,考虑的情况?这些想法可以帮助我们写出更好的代码。单元测试的粒度如果你是编程高手,单元测试似乎可以少写。王音大神在《测试的道理》中是这样说的:在我心目中,代码本身的地位就比测试高很多。我不忽视测试,但我不会本末倒置,过分强调测试。我不提倡测试驱动开发(TDD)。我知道什么要测试什么不测试,什么时候写测试什么时候不写,什么时候推迟测试,什么时候根本不测试。为此,加上我强大的编程功底,我多次完成了别人认为不可能在短时间内完成的任务,产出了非常高质量的代码。那么问题来了,你是高手吗?根据第28条原则,大多数开发人员都不是大师。我自认为我的编程水平还不错,我也尽量选择写单元测试。假设你是高手,你能保证你的团队是高手吗?根据28原则,一个团队中只有少数人是高手。如果你没有编写足够多的单元测试,它们会弄乱你的代码,事情就会发生。所以,还是要尽量写单元测试,不管你是不是高手。当然,不能没完没了地写单元测试,否则就是本末倒置。另外,单元测试越写越多,边际收益在不断降低,得不偿失。神奇的28原则告诉我们,20%的测试可以覆盖80%的问题;而剩下的20%的问题,你需要写80%的单元测试。换句话说,单元测试并不能消除所有问题。因此,对生产代码进行实时错误监控是非常有必要的,这也是我们Fundebug正在努力做的事情。在《单元测试要做多细?》中,鼠哥告诉我们:UT的粒度不重要,重要的是你会不会去想你的软件应该怎么做,怎么去测试。这是每个程序员都应该认真思考的问题,并没有所谓的标准答案。从小受中庸之道和唯物辩证法教育的我们,应该能够在实践中思考合适的测试粒度。当你学会思考,你就能成为真正的高手。参考Fundebug:玩转Node.js单元测试Fundebug:JavaScript有几种测试王音:测试原理CoolShell:单元测试应该做多详细?版权声明:转载请注明作者Fundebug及本文地址:https://blog.fundebug.com/2017/12/20/rethinking-unit-test/
