未经测试的东西都会出问题
高级测试技术
所以到底是怎么回事?
结论
坦白说,我是测试的忠实拥护者,尤其推崇测试驱动开发(TDD)。而且,我主要指的是专业软件开发(也就是你实际编写的代码能带来报酬的那种)。这篇文章是写给那些不做测试或者不被允许做测试的人的。令我惊讶的是,竟然还有那么多公司、部门或团队没有正确地测试代码。
但我为什么要测试呢?因为所有未经测试的东西都会出问题,这只是时间问题。
需要明确的是,我并不是说 100% 的测试覆盖率是最终目标,因为总有一些代码是绝对不能出错的——比如一个简单的实体类。但是,其他所有代码都应该在某个阶段被测试到。我也不是说所有未经测试的代码都会有 bug,但是一旦出现 bug,它很可能就存在于代码库中未经测试的部分。正因为你无法预知 bug 会出现在哪里,所以编写测试可以降低 bug 发生的概率。
为此,我目前已经见过许多经过实战检验的有效方法:
高级测试技术
编译器驱动设计(CDD)
我在之前的“WTF”文章里提到过这一点。实践CDD的时候,你必须是个非常乐观的人。你会去Stack Overflow上查资料,复制粘贴,修改代码,然后注释掉一些部分,直到代码能编译通过为止。
任务完成。继续。咱们看些猫咪视频吧。
基于信念的设计(BBD)
这是CDD(代码驱动开发)的一种变体,在这种变体中,你通过检查代码并在你的电脑上运行一次,就非常确定代码中没有任何错误。它肯定能在生产环境中正常运行。
我们来看一些猫咪图片,然后想想……
让客户测试
好主意!如果你运气好,真的有客户在,那就让他测试软件,找出并报告bug。这不就是保修的意义所在吗?不过,你得确保客户会因为没用你的问题模板而被你拒绝提交bug报告而感到不爽。
手动测试
现在我们进入专业阶段了!你竟然可以自己进行一些测试!是不是很激动人心?
你在看猫咪图片的时候,点击了你刚刚实现的按钮,它居然能用!干得漂亮!接下来我们来实现下一个功能。
客户打电话过来,你因为他愚蠢而对他大吼大叫,因为你确实测试过了,然后你挂断了电话。
你现在还不知道的是,在实现第二个功能之后,你实际上破坏了新按钮的代码。
通过惨痛的教训,你计划在新功能上线时测试所有旧功能。你把所有需要测试的内容都写下来,以便下次参考。我们称之为测试规范。
但随着产品规模的扩大,测试耗时过长,你根本无法承担。你的经理因为你不再编写功能代码而对你大吼大叫。
然后,你的产品即将发布,客户翘首以盼,但你却无法完成所有功能,包括测试。于是你跳过了一些测试,但你却隐隐感到不安,觉得哪里出了问题。
你说得对,顾客一用你的产品,它就坏得很厉害。找到故障原因只需要几天,但修好却要两周。
所以到底是怎么回事?
我们刚才看到了软件开发中的一些陷阱。我们该怎么办?依我拙见:测试。
但测试什么呢?
测试范围
您可以测试不同的范围:
| 测试范围 | 抽象层 | 成本 | 描述 |
|---|---|---|---|
| 单元测试 | 低的 | $ | 它们测试的是代码的“单元”,通常但不一定是一个类。单元测试覆盖系统的大部分,尤其是所有边界情况和错误情况,因为它们更容易建模。这就像用放大镜检查汽车的焊接点一样。 |
| 集成测试 | 中等的 | $$ | 它们会选取系统中较大的部分(即多个单元)作为整体。它们通常测试多个类与外部接口(包括 HTTP、数据库、消息队列等)之间的交互。这些接口会通过内存或进程内替代来模拟,从而允许在无需任何外部系统的情况下在计算机上运行测试。集成测试可以加速运行,以便在单元测试套件中完成。它们就像检查门是否真的能装入机箱一样。 |
| 系统测试 | 高的 | $$$ | 他们将整个系统视为黑盒进行测试,通过与外部接口(例如 UI、HTTP、MQ 等)交互来实现。这类测试编写成本最高,运行时间也最长,因此通常只对主要业务用例进行端到端测试。拥有一个与生产系统完全相同或至少非常接近的测试环境至关重要,包括服务器配置、操作系统、数据库等等。系统测试就像检查汽车是否真的能够行驶,以及踩下刹车踏板时刹车是否真的能使汽车停下来,而不会发生其他故障一样。 |
| 验收测试 | 特征 | $$$$ | 它们主要涵盖与客户或产品负责人相关的主要用例。其理念是,这些测试可以由客户编写,一旦测试通过,该功能即被验收。您可以将其理解为:我的车可以在 30 分钟内将我从家送到公司,油耗为 8 升/百公里(50 英里/加仑)。 |
还有一些其他的测试,例如性能测试、耐久性测试和冒烟测试等等,这里我就不一一介绍了。
从上到下,编写测试的工作量和成本通常会增加,而测试的细节程度则会降低。Martin Fowlers 在《测试金字塔》一文中提到了这一点。
测试范围的把握至关重要,但也极具挑战性。在所有范围内覆盖 100% 的代码是不现实的,这需要耗费太多精力。因此,你需要始终决定在哪些范围内测试哪些内容。这始终是一个权衡取舍和经验积累的过程,但你几乎不可能只在一个范围内构建软件。由于单元测试编写起来更容易、成本更低、速度更快,你应该尽可能多地使用单元测试来覆盖代码(这里强调“尽可能”,因为你需要更高抽象层次的测试,例如集成测试,是有原因的)。
从理论角度来看,我非常赞同允许客户或产品负责人使用易于阅读的领域特定语言编写验收测试的想法。然而,实现这一点的难度相当大,而且说实话,我们从未真正使用过自动化验收测试。我们只是在系统测试中涵盖了与验收相关的用例。
自动化与速度
测试最重要的就是自动化和快速。
自动化有助于消除人为错误。它必须足够快,以便在每次代码更改后都能运行测试,从而获得即时反馈。单元测试应该在几毫秒内完成,整个测试套件也应该在几秒钟内完成。集成测试也应该在几秒到 30 秒内完成。系统测试可能需要几分钟才能完成,但只有在真正必要的情况下才需要这样做。
自动化带来的一个优势是,它还可以运行测试覆盖率分析,显示代码中未覆盖的行或分支。如上所述,不要试图达到 100% 的代码覆盖率。使用覆盖率工具可以帮助你发现可能遗漏的内容,例如某些else分支,并结合常识进行判断。
仅供参考,我们团队的覆盖率通常在 60% 到 80% 之间。
回归测试
自动化测试尤其有助于发现回归错误。在实现新功能、修改代码或重构代码后,只需运行测试套件,即可检查是否破坏了之前运行正常的功能。这与手动测试截然不同。在开发过程中或开发完成后手动测试单个功能看似足够,但有时新功能会破坏一些旧功能。
你可能已经进行了非常彻底的测试,一切似乎都没问题,然后你只是修改了一行不起眼的代码或某个配置值,而这些改动并不会造成任何影响。但这也会不时地引入新的 bug,尤其是在你意想不到的时候。
编写代码和API文档
我不太喜欢代码注释。通常情况下,我会删除所有注释,并用命名规范的变量、方法或类来替换它们。这叫做代码整洁。
然而,代码文档的编写确实必不可少,但必须避免冗余和自我贬低。
我认为,解决方案在于测试。编写易于理解的测试,并正确命名,使用Given-When-Then结构。记录类或API的最佳方式是编写实际使用该API的代码。因此,为什么不花些精力编写可读且可执行的测试,作为代码的规范呢?您甚至可以从测试中生成REST API文档,包括真实的请求、响应和参数。
减轻压力
对我来说,自动化测试套件最重要的优势,也是我认为最被低估的优势,就在于此。它能在压力巨大的时候保护你,比如临近发布日期,或者经理对你大吼大叫的时候。我经历过很多次这样的情况。在我职业生涯的早期,我还没有一套完善的测试套件,当时我需要快速修复一个可能影响时序并导致并发问题的代码。我记得当时我匆匆忙忙地测试了一下,然后就把代码交付给了客户,额头上冒出了冷汗。我努力思考代码库中所有可能出现的问题,以及可能造成的破坏。
结果可想而知,它失败了——在生产环境中。
几年后,我遇到了类似的情况。不过,我这次采用测试驱动开发(TDD)的方式开发了整个软件,最终得到了一套庞大的测试套件。从软件上线那天起,就没有任何bug。在添加了一个紧急的新功能后,所有测试都通过了,所以我发布时非常放心。一切都很顺利。
客户满意度和维护成本
大多数客户,以及——很遗憾——大多数不熟悉软件,尤其是软件项目的管理者,如果你问他们是否愿意花钱进行测试,他们可能都不愿意。
然而,如果你问他们“这款软件是否应该保持高质量”,他们中的大多数人可能会回答“是”。但如果你问他们是否愿意为维护、项目保修期内的漏洞修复或产品中功能缩减而付费,他们会回答“不”。
所以,一段时间后,你会明白以下几点:
- 无论之前经理和客户怎么说,他们都期望软件质量高、没有漏洞。
- 要实现这一点,你需要编写测试。
- 你永远不应该主动要求写测试,直接去做就行了。对管理者和客户来说,这只是无关紧要的背景噪音。
- 如果有人问你为什么某个功能还没完成,千万不要说“我正在写测试,比我想象的要花时间”。要告诉他们,这个功能之所以花费的时间比你预想的要长,是因为测试代码和生产代码必须放在一起。
说清楚点:我不是教你如何欺骗别人。我坚信,测试在生产和维护周期内能够节省成本。它能减少生产环境中的缺陷数量,让客户满意,从而增加再次订购的可能性,并可能成为客户的推荐客户。低维护成本也能让经理满意。
软件设计
对我来说,最大的优势在于最终的软件设计。我指的是类、方法和接口的呈现方式。这种优势主要源于测试驱动设计(TDD),因为它迫使你编写的代码必须符合之前编写的测试用例。如果不采用TDD,你经常会遇到这样的情况:某些类、方法或代码分支几乎无法测试。
缺点或例外情况
说实话,我没看到多少这样的问题。实际上,唯一的问题是,重构代码后,很多测试用例可能无法编译。这很烦人,也可能比较耗时,但大多数情况下很容易修复。我绝不会为了重构后不用修复测试而放弃测试带来的好处。
那么,规则也有例外吗?我会为副业项目做测试吗?大多数情况下会,但不会像在正式工作中那样彻底。那试点项目或概念验证呢?实际上,我不相信这种东西。我从未见过哪个客户真正理解“这只是个概念验证,不做测试,一旦拿到正式订单,我们就把所有代码都扔掉”这种做法的后果。他们最多也只是想要功能少一些但无bug的软件。所以,无论你开发什么软件,只要是用于专业用途(客户或内部生产),就必须进行测试。
记住,要了解客户的期望。
结论
编写测试是强制性的,未经测试的生产代码将会失败,而且根据墨菲定律,它会在压力很大的情况下失败,因为你没有时间进行彻底的思考。
测试有利于软件设计,减轻压力,提高质量和客户满意度。
立刻行动,别找借口。
原文发布于我的博客 return.co.de
封面图片取自这条经典推文:
感谢@kylegalbraith向我推荐了 Martin Fowlers 的文章!
文章来源:https://dev.to/stealthmusic/everything-thats-not-tested-will-break-1adg