转载

为什么要测试,测试是如何令人更快乐的?

为什么要测试,测试是如何令人更快乐的?

英文原文: Why and How Testing Can Make You Happier

我曾经是一个不测试主义者,因为我看不到测试的价值。然后,我试了一段时间,变得对它深信不疑。我收集了一些经验,当然还远远不够。这篇文章总结了一些我知道的以及我认为我知道的内容。

本文的灵感主要来自于《JavaScript Air episode 004》,但这里也有一些原创的内容。并且有的来自《TDD: Where did it all go wrong?》。

我不总测试我的代码,但是当我测试的时候,感觉更好。

—— 我

这是怎么一回事呢?

这,全是因为代码:

本文主要关于单元测试,而不是集成测试或端至端的测试,但在某些方面也可用于其他测试。在实践中,测试很少是单一的或非此即彼的,并且这也不是目的。

单元测试成本低廉,因此应该成为测试工作中最大的组成部分。编写和运行单元测试都很便宜。因为它只查看代码的特定部分。集成测试则相反,它们包含的代码更大。

为什么这很重要?

测试可帮助你对你的代码放心。对一个稍复杂的问题写一个解决方案,然后手动测试,你只需要这么做就可以了。有着一定经验的你当然可以自信地发布代码,但是结果却往往是抛弃了发现错误的第一次机会。

测试能让你体验你的代码中在最极端的条件下是什么样的。要是传递的数字是负数,会怎么样,在我们总是假定数值为正的情况下?要是传递的根本就不是数字,会怎么样?

每个人都会写出 bug,我们都写过 bug。因此,这不是“你能正确地编写代码或一次性写出正确代码?”的问题,我们都写过不正确的代码。这就是我们所做的一切,我们写的都是不正确的代码。

——Joe Eames《JavaScript Air 004》

编码是辛苦,我们都应该承认这一点。其中的主要原因之一就是,你需要测试代码,以获得它能如期表现的信心,不管是什么代码。

不相信?这里给出了一些附加参数:

测试过的代码更好

许多人会告诉你,代码测试会导致更好的代码质量。这在使用单元测试,并且至少在测试驱动开发上有所行动,即使这些行动甚为草率时,尤其如此。原因如下:

如果你的代码难以测试,那么可能是你代码没有写好。好代码的定义是什么,这是一个大问题,但这里要强调的一句话是一个很好的经验法则,也是大多数人所赞同的,那就是,好的代码会分离关注点。有经验的 程序员 限制功能体以便于只做一件逻辑上的事情就是这个原因。

目标对齐

代码很难测试可能要么是因为有太多的事情要继续,要么是因为有太多的依赖(或两者皆有)。考虑将此视为协调利益的一个问题:在编写未经测试的代码时,在速度(或懒惰)和关注点分离之间存在着利益冲突,并且短期内你的代码是如何被组织的并没有那么重要。当代码必须测试时,你的目标更一致,因为对于写得好的代码,更易于写测试!

像消费者一样思考

当你第一次编写测试时,你首先要设计代码的 API。测试让你进入代码消费模式,在这种模式下,你的代码需要面对其他东西的接口。设计 API,而不那么关注内部运作将导致一个更佳的 API 设计,这会导致模块的更易消耗,从而促进项目代码的更干净。

灵感突现

测试会让你灵机一现。通常情况下,因为它迫使你去思考边缘情况——零值,10 ^ 12,null 或 undefined。这使得你有机会来思考。去反省,以及了解在陌生的环境下会发生什么。仅仅是思考这些的过程,或代码将面对的其他情况,都经常会让你意识到代码可以简化(以及代码需要如何保护自我)。

这些灵感突现的时刻也可能来自最令人沮丧的情况之一:当你的代码和测试不一致的时候。你正处于不知道哪个才正确的两难境地。如果你碰到这种情况,那么设计可能有问题,或者你的前提假设发生了变化。把它看成是一个好兆头!你的代码将会更满意。

测试可以说明代码做了什么

没有人喜欢写文档,但当你继承(从一年前的自己,或其他人)或接口的模块文档齐全的时候,绝对是好的。测试可以成为这样一种途径,并且还有一个额外的好处是:测试用实际行动证实代码。就如同最佳的科学教师,他们不只是用嘴巴告诉你,氢气易燃,而是充了一个氢气球,让它升到天花板上,然后在棍子上放一根点燃的火柴靠近气球(这是我五年级时最难忘的时刻之一)。

你知道所有 bug 的共同点吗?那就是它们通过了所有的测试。所以,当你找到一个 bug 的时候,就等于知道测试哪里还需要改进。

为什么要测试,测试是如何令人更快乐的?

测试可以使得更容易地加入项目,因为它们揭示了代码实际上应该做什么。它们告诉你设计决策,以及初始的开发人员心里在想什么。

不要担心,去重构吧

也曾看到过乌七八糟的代码,但不敢去清理干净?我在这种情况下要做的第一件事是创建测试来找出代码要做什么。测试可以锁定功能,用一种很好的方式,使得我们能够专注于“大扫除”,而不是担心破坏什么东西。

我见过一些糟糕到让人不知道它们是做什么的代码片段。同样的,人人避之唯恐不及,不但要担心会破坏预期的功能,而且还要担心破坏 bug。我认为基于过去的I/ O 的大型测试集是非常值得的投资。

有趣的是,担心和快乐的心情是成反比的。总之是一种此消彼长的状态。

自信地创造价值和正确的产品

正确的代码比不正确的代码更有价值。一切帮助你的代码比以前更正确的东西都值得看一看,就这么简单。发布正确的代码随着时间的推移会构建起信任,而信任是一笔宝贵的财富。

鱼与熊掌不可得兼

这里有一个技巧:不要在试图解决问题的同时,设计一个很好的解决方案。来自于 Ian Cooper 关于 TDD 演讲中的秘诀是:

  1. 编写红色测试。
  2. 解个问题,尽快让它变绿。
  3. 设计一个很好的解决方案,重构成你为之骄傲的一个东西。

这里要掌握的一个重要内容是,在你的大脑中要分离关注点。不要试图同时完成步骤 2 和步骤3。编程的主要限制之一是你的大脑一次能思考多少,并且在你敲代码时,你需要思考得越少,你写的代码越好。

在解决问题时,不要去想代码实际上应该如何。复制粘贴代码,写低效的循环,重复内容,不论是什么只要能尽快让测试变绿就去做。然后再考虑如何改进。

分离关注点是首先要测试的原因之一,这种方法有助于实践中行为。当你不择手段地想要快速达成一个解决方案时,你不必去考虑它看上去怎么样或者运行起来快不快。当你进行到完善设计和改善解决方案的时候,你就不必担心解决方法行不通了。

知道测试什么是关键

知道测试什么没有听上去得那么容易,并且有很大一部分是由经验所决定的。许多测试测试得太多。知道要测试什么涉及到要了解什么重要,什么不重要,而要知道这些并不是一件随随便便就能做到的事情。这里有一个技巧,但:

尽可能采用最高级别的测试,以便于在实现上覆盖范围和灵活性。

——Brian Lonsdorf,《JavaScript Air 004》

所以,基本上:

  1. 不要测试内部的东西,这只会成为你的阻碍。如果你真的觉得你应该测试内部的东西,那么你最好分离成一个新的模块,使之成为外部的东西。
  2. 不要测试过于指定,或处理它们不必和不应该知道的东西。
  3. 不要只是为了获得 100% 的覆盖率而去写测试。如果有人告诉你应该保持 100% 的覆盖率,那么不要废话,揍他。

请记住,测试应该从模块外部的角度开始由外到内。需要注意的是完全覆盖的测试还是有可能的,即代码的所有分支应该都可以实现。如果没有,那么它们基本上是死码,不是吗?除非你需要更好地理解它们是如何工作的,否则就不要测试内部的东西。

想想当一段时间以后,代码重构的时候,会发生什么。实现应该允许在测试不失败的情况下被更改。为什么?因为如果将来的程序员需要改测试的话,那么基本上是重写,而不是重构。并且重写并不安全。对于重构内部应该没有新的测试。

在测试时要务实。测试是项目以及创造价值的一部分,什么都拿来测试没有任何意义,就像实现所有按钮没有意义一样。记住文档方面。如果测试涉及许多实施细节,那么我们就会失去模块的重点。我们就会失去文档的价值。

至于文档,测试你的领域假设。这些都是你工作的问题域的代码解释,这些问题域往往是一些程序员不擅长的地方。用代码的形式文档化这些假设解决了两个问题:自我文档化假设,并证明它们能够如解释那样有效工作。

当你发现 bug 的时候,编写测试。不要只是修复它。去写测试,确保它既是红的,又对齐 bug 所没有意识到的期望。修复 bug,使其呈现绿色。保存。

代码覆盖作为一个具体的数字被高估了,但作为一种工具它还是很有用的。不要为了覆盖范围而力求覆盖。请记住,覆盖范围只能告诉你测试在代码行运行什么,而不会告诉你测试将运行什么组合。不过,这可以成为事情是否朝着正确方向前进的一个很好的风向标。如果重构导致更糟的代码覆盖范围,那么就应该响起警铃,尤其是如果它是重构的话。不要只是为了增加覆盖数值就让自己去编写测试。经过充分测试和编写良好的代码的覆盖数值更大。

编写测试的触发器是当你的代码片段有新的行为的时候。测试应该盯牢这种行为,但不要矫枉过正。

测试库可能比测试终端应用程序更容易,更为重要。毕竟,库会被多个应用程序使用。

如何编写特别棒的测试

知道如何写出好的测试是关键,因为很容易写得不好。事实是,和其他所有一切一样,它需要实践。不过,这里有一些小贴士。

好的测试往往是简单的。它不会尝试一气呵成面面俱到。它的名字反映了它要的目的,并且名称应该精简成一句话。例如,名称不应该是“it works”,而是“it returns 0 for negative values”。

为什么要测试,测试是如何令人更快乐的?

确保测试不要过于指定。过于指定的测试涉及到太多内部东西,并且不允许重构。

单元测试运行代码时会隔离其他测试,不一定是其他代码的测试。它将代码带出它的上下文,并创建其中一个方面的人工上下文,以便于进行调查。然而,这并不意味着单元测试必须得在隔离其他所有代码的情况下运行,尽管这通常被认为是“纯单元测试”。所有一切都没有必要 mock 和 stub,因为只会导致更复杂的设置,更低的覆盖率和更加脆弱的测试。

在有意义的地方使用 mock 和 stub。你不想对一个真正的 HTTP API 进行测试,那就 stub。如果你正在测试的东西是你自己对该对象的调用,或你想要自己的代码历经某个路径,那么使用使用 mock 和 stub。

测试读起来应该像一个小故事,遵循 AAA 体系: Arrange、Act、Assert。设置东西,做出声明,并且断言声明做了它应该做的。“小故事”方面要重视小的方面。“3A”中没有一个应该超过 3 行代码以上。在阶段之间留一些空间会更好。应该没有任何分支和循环,你在断言时应该只涉及一个逻辑内容。 (如果一个断言语句就能表达自然是好,但有时你需要更多,那也没关系。)永远不要在测试的两个不同的地方断言,因为这会导致你实际测试的混乱。

测试应该只需要一些领域知识就可读。如果不深入模块的内部运作就很难解释的话,那么要么你最好多花一些时间在测试上,那么彻底弃之不顾。

一般情况下,不要测试依赖。对于某些项目,对一些代码所做的假设做一些简单的测试,可能是有意义的,但要谨慎和小心。测试库是库作者的工作。相反,要依靠更新日志进行升级,以及依赖于测试集成而不是库(不用 mock 一切的一个原因)。

编写不需要很长时间运行的低成本测试,因为要时常运行这些测试。如果你可以传递 --watch 参数到你的测试运行中,并且在每次有文件改变时运行它,那么这是一件好事。

最后但并非最不重要的一点是,使用你喜欢的测试框架。如果 JavaScript 是你的菜,那么我会推荐 AVA,因为它清晰简单,而且没有复杂的配置。不管你选择什么,确保测试框架能和你一起工作,并帮助你编写测试更高效,更快捷。正如编码一样,如果你觉得不好玩,那么可能有什么地方出错了。

-

译文链接: http://www.codeceo.com/article/why-test-how-make-happy.html

翻译作者: 码农网  – 小峰

原文  http://news.cnblogs.com/n/544762/
正文到此结束
Loading...