自测试代码
2014年5月1日
自测试代码是我在《重构》一书中使用的一个术语,指的是在编写功能软件的同时编写全面的自动化测试。如果做得好,您只需调用一个命令即可执行所有测试,并且您有信心这些测试将揭示隐藏在代码中的任何错误。
我第一次接触到这个想法是在一次 OOPSLA 会议上,我听到Dave Thomas(人称“Beddara”)说每个对象都应该能够自我测试。我突然有了一个想法,输入一个命令,让我的整个软件系统进行自检,就像你在启动时看到的硬件内存测试一样。很快,我就在自己的项目中探索这种方法,并对它的好处感到非常满意。几年后,我和 Kent Beck 做了一些工作,发现他也做了同样的事情,但比我做得复杂得多。这距离 Kent(和 Erich Gamma)推出 JUnit 不久,JUnit 是一种工具,它成为了自测试代码(及其姊妹方法:测试驱动开发)的许多思想和实践的基础。
当您可以对代码库运行一系列自动化测试,并且确信如果测试通过,您的代码就没有任何重大缺陷时,您就拥有了自测试代码。我认为这就像在构建软件系统的同时,也在构建一个能够检测系统内部任何故障的错误检测器。如果团队中的任何人不小心引入了错误,检测器就会发出警报。通过频繁地运行测试套件(至少每天几次),您就能够在引入错误后很快地检测到它们,这样您就可以只查看最近的更改,这使得查找错误变得*容易得多*。任何编程工作,如果没有工作代码和保持其正常工作的测试,都是不完整的。我们的态度是,任何没有经过测试的非平凡代码都是有问题的。
自测试代码是持续集成的关键部分,事实上,我认为除非您拥有自测试代码,否则您并没有真正进行持续集成。作为持续集成的支柱,它也是持续交付的必要组成部分。
自测试代码的一个明显好处是,它可以大大减少生产软件中的错误数量。其核心是建立一种测试文化,让开发人员自然而然地想到将代码和测试一起编写。
但最大的好处不仅仅是避免生产环境中的错误,而是您对更改系统的信心。旧的代码库通常是可怕的地方,开发人员害怕更改工作代码。即使是修复错误也可能是危险的,因为您可能会创建比修复更多的错误。在这种情况下,不仅添加新功能的速度慢得可怕,而且您最终还会害怕重构系统,从而增加技术债务,并陷入一个不断恶化的螺旋,每次更改都会让人们更加害怕更多的更改。
有了自测试代码,情况就不同了。在这里,人们有信心可以安全地修复小问题以清理代码,因为如果您犯了错误(或者更确切地说“当我犯了错误”),错误检测器就会发出警报,您可以快速恢复并继续工作。有了这个安全网,您就可以花时间保持代码的良好状态,并最终进入一个良性循环,您添加新功能的速度会稳步提高。
人们经常会在谈论测试驱动开发(TDD)时提到这类好处,但将 TDD 和自测试代码的概念分开是有用的。我认为 TDD 是一种特殊的实践,其好处包括生成自测试代码。这是一种很好的方法,TDD 也是我非常喜欢的一种技术。但是,您也可以在编写代码之后编写测试来生成自测试代码,尽管在您完成测试(并且测试通过)之前,您的工作不能算完成。自测试代码的重点是您拥有测试,而不是您是如何获得测试的。
实践自测试代码的团队的一个重要行为是对生产环境中错误的反应。使用自测试代码的团队通常的反应是首先编写一个暴露错误的测试,然后才尝试修复它。通常,编写这个测试实际上可能是一系列测试,逐渐缩小范围,直到您找到一个触发错误的单元测试。这是一种有用的调试技术,它对于确保错误一旦修复就不会再次出现也是必不可少的。通常,团队还会利用这个错误作为灵感,寻找类似的缺失测试。态度应该是,任何错误都不仅仅是代码中的失败,也是测试屏幕中的失败。
如今,我们越来越多地看到自测试的另一个维度,即更加重视生产环境中的监控。持续交付允许您快速将新版本的软件部署到生产环境中。在这种情况下,团队会更加努力地在生产环境中发现错误,并通过部署新的修复版本或回滚到最后一个已知良好的版本来快速修复错误。
这篇文章最初发表于 2005 年 5 月 5 日(篇幅要小得多)。