为什么很多程序员不喜欢写单元测试?

最近在学习node,看了些资料,说到了代码测试问题,个人认同这个观点,可是心里找不到足够克服懒惰的理由,到底为什么要写测试(尤其node),希望可以深入的解释,从各个方面谈一下,包括对程序员,对项目的作用。

阅读 14.3k
11 个回答

我认为这和懒惰与否没有关系,测试不是必须要写的,有人说:雇主不是为测试而付钱给我的(大意),这话的意思是如果客观条件不允许(比如时间紧张)或者没必要(比如目标代码你闭着眼睛都能写得很好)等情况下,测试不写也就不写了,没什么大不了。

如果你觉得自己“懒得写测试”,那么你必然有“犯懒”的理由吧?时间紧或没必要,你占了哪一条?

如果哪条都不沾,那或许是你不懂如何写测试——我指的不是技术层面上的“懂”,而是说如何让测试行之有效又不过度耗费你的精力——这就涉及到测试能带给我们些什么好处了。

对于程序员个体而言,测试最大的意义在于“建立和维持对代码的自信心”,这一点大致可分为两个层面:

  1. 从无到有的时候。如果你对要写的代码无比熟练,挥手即来,那么测试对你的意义微乎其微(此处假定代码的编写和维护都是你一个人,不牵扯协作开发者。并且就算有牵扯,测试的必要性也要视乎你们的协作方式,这个后面再说)。可如果你对要写的东西很陌生,脑袋里一团浆糊的时候,测试就可以变成你的“引路人”。简单地说,就是把复杂的大问题拆分成简单的小问题,然后用测试描述每一个小问题的输入输出,接着失败 => 代码实现 => 成功,如此循环往复,最终完成了整个解决方案。
    显然,不写测试也可以这么做(指拆分复杂问题的方法论),但是测试会有一些好处:

    • 每一个测试只对一个问题单元负责,哪里不通过哪里有问题,这有助于把复杂逻辑解构成若干简单的模块化的代码。等到重构的时候你就知道这有什么好处了。没有构造良好的测试,没人愿意重构。这对于代码的维护是很有帮助的,除非你是一锤子项目,那就无所谓了。
    • 因为测试先于代码编写(其实就是 TDD),所以对于接口的设计往往是先于内部实现完成的。这是一种很好地代码设计习惯,然而虽然人人都知道但大多数人总是做不到。测试则会间接的“逼迫”你先考虑接口设计,再关注具体实现,若你的接口设计有问题,通过先写测试很容易就能察觉到,避免写一大堆代码之后才发现这么写不合适。
  2. 从有到优的时候。刚才提到重构,除了维护代码需要重构,修补 Bug 或是增加新的业务逻辑也算是重构的一部分(不太严格的说)。测试本身就是对业务逻辑的实例化描述,你的测试能否覆盖到业务实际的范围取决于你对业务本身的了解程度有多深,不过反过来就算你有遗漏或者理解上的偏差也没关系,因为那些正确的测试已经保证了你实现的正确的部分。接下来对于错误(bug)你只需要修正有问题的测试或者补充没有覆盖到的逻辑;而对于新增加的业务也是一样的道理,扩充测试用例即可。当然最后一步是让都 pass。如果你测试写得好——我是说语义良好,那么它几乎就可以拿来做需求文档了。回头 PM 说你那儿那儿做得不对,你可以搬出测试给他/她逐条讲解,很容易就能知道你们之间的误差在哪里。
    除此之外就是针对代码质量的重构了,没有测试的时候最头疼的问题就是不敢随便对代码动刀子,因为你没有可靠的质量保障,有了测试就好多了(不能保障百分之百不会出岔子,毕竟测试也是人写的)。这方面就懒得多说了,自己经历几次就能体会。

总而言之一句话,对于个体而言,测试就是你对业务逻辑的理解在“纸面”上的体现,另外它可以促使你提高代码的质量,提高代码设计的水准。不过总的来说还是“锦上添花”而不是“雪中送炭”,不写测试的项目多了去了,也不见得就都会死掉。写不写测试,一则视客观条件,二则视主观要求,我的态度是不强求。至于那些说写测试降低效率的,纯属扯淡。我就这么说吧:

如果你觉得写了测试对自己没有什么帮助反而降低了开发效率,那你就别写了——因为你不会写测试(还是的,非技术层面的“不会”,而是理解层面的)。

再把眼界放宽些,如果你不只是一个个体,你或许维护着一个全世界很多人都在使用(或将来可能会使用)的开源项目,那么我的建议是:必须写测试!

这种情况下,测试不只是写给你自己看的,甚至都不只是写给你的团队成员看的,它更大的意义是写给“外人”看或者用的。有人说“我们会出文档啊”,nonono,文档是给“消费者”看的,作为一个开源项目,你总是有两类“客户”。一类是拿来用的消费者,另一类则是改进改进让它变得更好的贡献者;后者有很多都是从前者演变过来的。

当你作为一个贡献者参与进来的时候文档对你没多大帮助,因为你的目标不只是会用,你还要知道它到底是怎么工作的,要如何才能改动它以适应更多的要求。这个时候测试就像指南针,撰写良好的测试就好像一个对项目本身了如指掌的大管家,一步一步手把手的指导你这个项目是怎样从无到有一直发展到今天的。没有测试?那你就抱着源代码一行一行抠去吧。而且事情往往是这样的:测试良好的项目,其代码本身也很清晰,干净,易于阅读和理解;反之,测试糟糕甚或是没有测试的项目,代码往往如同一团乱麻一般,除了作者自己任谁看了都会头大如斗——哦,对了,就算是原作者,放上三个月不碰回过头来还能认出自己写得啥不?


你可以不写测试,只要你理解了我上面所说的种种情况并能够做出合适的决定,但是不写测试绝对不应该和“懒惰”划上等号。恰恰相反,有的时候我接到一个需求,我还就是“懒得”想实现的细节,索性先从写测试开始一步一步抽丝剥茧,最后不知不觉的也就写完了。这其实反映了测试恰恰是为“懒人”设计的编写代码的方法,前提是你知道如何正确的编写的测试。

现在大部分的测试教程都有一个通病,那就是用了大量的无意义的测试来举例讲解。这么做可以理解,毕竟教程的作者又不知道你的实际情况,为了浅显易懂起见,自然还是那些无意义的测试更容易看明白。我得承认真正学会写测试是需要时间来练习的,有些东西真是只可意会不可言传。对于初涉测试的程序员来说,那些教程里的无意义的例子的确容易给人一种错觉:如果实际工作中也要这么写测试的话实在是太啰嗦太繁琐了!没错,一开始是这样的。一你得坚持练习,二你得找对方法;测试是为“聪明的懒人”准备的玩具。

推荐两个东西,一个是 Gary Bernhardt 录制的一系列视频,大量的讲解了实际项目中的测试理论和技巧,都是干货没有无意义的东西,但是有难度,要反复多看多体会:https://www.destroyallsoftware.com/screencasts/catalog

还有一个是来自于现实世界中一个非常有名的例子,一群人利用测试和重构挽救了一个差点不可救药的经典项目 Redmine,之后他们为这次大规模的重构写了一本书:Refactoring Redmine,满满的都是经验值啊。

写测试的优点很多:代码更有条理,易于维护,保证质量,便于梳理思路。
但不写测试的优点只有一个:省出时间玩游戏。

建议不同的项目不同对待。

因为以后的代码修改,不是自己来做。

  1. 认同而不找机会做,这是自我管理的问题,建议看《自控力》

很多情绪问题,其实是经验问题。如果你不反感这句话,不妨继续看。

  1. 为何node特别需要?

因为node是javascript,是脚本语言,缺乏很多基本的静态检查代码的能力。所以需要动态的运行时的测试去补。

对应的,静态语言,就可以做很多检查,避免程序员犯基本的二错误。特别是类型错误。

3.所以,测试用例很万能。

不管静态语言,还是脚本语言,都无法检查程序员的逻辑(语义)错误,而测试用例可以。

测试用例还可以让你从程序入口,到你正在关心对代码的路径最短,你不关心的代码可以被临时短路。

我就看到很多程序员,就填了几行代码,就测试一个简单的逻辑,需要编译,执行,登陆,点击x菜单,y按钮。终于到了断点。检查值。嘿,特么的怎么不对?。改两个字母,Loop。Loop。Loop。

真是,勤奋的只能勤奋。

在用户那里,代码的路径是固定的。而对程序员来说,路径可以很多,可以临时修一个快速通道,用完关闭,或者短路,多好玩。

那些个点击操作,和你的问题有关吗。无关,就特么短路它!

只能按照用户的路径去走,真是如同周小平一样,辜负了这个时代。

不写测试用例 ,很多时候,是没有身处这样的环境:就是写代码的最大时间消耗在于troubleshooting,或者:懒得寻找可以偷懒的方法,勤奋的用着只能勤奋的方法。

  1. 对项目的价值

给你一个win32 的api,也有文档,MSDN内可多。但是就特么不给你例子,一个也不给。你感受下。

当一个程序员看别人代码的时候,每个函数,类都在,代码也在,但是看不到如何引用,如何使用,怎么传参数,会返回什么,不能传什么。——这个代码看起来真是抓狂。还别说,代码都在,你可能也总是看不懂。

真实代码中,当然会有引用,但是因为逻辑的要求,或者代码的混乱(当然更多是代码的混乱),分布在1200个地方,110个传值点,42个结果字段,78个引用。蜘蛛精一样的代码,见过没有?

有一组测试用例,真好,集中在一起,既把这个分支cover到,有可以集中了解逻辑和服务,处处都润贴。比文档还好,比案例还好,还跟着代码一起走,还常常更新。活思想。

测试大法好。

認同了測試必要性卻找不到克服懶惰的理由,這只有兩種情況:

  1. 你並沒有深入理解,只是自以爲懂了實際上沒有,所以瞞不過你的「心」。
  2. 你深入理解了,「心」也動搖了,這時你的心爲不被改變而最後掙扎,終於給出了一個你無法拒絕的理由。

由此:

  1. 很簡單,代碼測試的必要性就如同科學實驗的必要性。邏輯上對不見得就對,邏輯上錯一定是錯。
    也可能邏輯錯了你沒發現?實驗會告訴你。當然實驗對不代表邏輯一定對,所以要不停地實驗,不停地糾錯,這樣才能趨於完美。
  2. 如果你深入理解並認同了測試的必要性,卻仍覺得「心」給的理由無法反駁,那麼二者之中必至少有一個是錯的。

就一个字儿:“懒”

新手上路,请多包涵

本质上是因为投入产出比不高

因为不会。。。

如果你知道你写的代码大多数都不会应用在实际项目中,或者很快被后面的代码覆盖掉,你就懒得测试了。

再说不少公司都有专门的测试人员,开发人员的思维就是,“测得出bug是他们的本分,测不出来是他们无能”。

首先,如果我把功能分解成很细小的模块,并且对每个模块的运行过程和输入都了然于胸,在这种情况下还做单元测试,也许是因为我是个极端的教条主义者。

其次,如果一个模块复杂到要做单元测试,说明它的复杂度已经超出了便于修改和维护的限度,在这种情况下,模块需要进一步被分解,分解到怎样的程度才令我满意呢?衡量的标准之一就是要不要做单元测试,所以,一个设计得令人满意的模块是一个不需要单元测试的模块。

最后,如果我真的相信我的代码能正确执行,为什么我还要用单元测试去验证它的正确性呢?如果我不相信自己的代码,或许我不应该从外部去测试它,而是从内部仔细审视它,确保它没有超出我预期的行为。

那么在什么情况下会用到单元测试呢?如果我要用别人的代码,可是我发现他/她的代码难以读懂,可是在使用前我还想确保他/她给的我的代码能正常工作,在这种情况下,单元测试会很有用。

宣传栏