19.3. 单元测试

过去,开发人员很少编写测试。如果测试是由一个独立的 QA 团队编写的,那么它们就是由一个独立的 QA 团队编写的。然而,敏捷开发的原则之一是测试应该与开发紧密集成,程序员应该为他们自己的代码编写测试。这种做法现在已经很普遍了。测试通常分为两类:单元测试和系统测试。单元测试是开发人员最常编写的测试。它们很小,而且重点突出:每个测试通常在单个方法中验证一小段代码。单元测试可以独立运行,而不需要为系统设置生产环境。单元测试通常与测试覆盖工具一起运行,以确保应用程序中的每一行代码都经过了测试。每当开发人员编写新代码或修改现有代码时,他们都要负责更新单元测试以保持适当的测试覆盖率。

第二种测试包括系统测试(有时称为集成测试),这些测试可确保应用程序的不同部分都能正常协同工作。它们通常涉及在生产环境中运行整个应用程序。系统测试更有可能由独立的质量检查或测试小组编写。

测试,尤其是单元测试,在软件设计中起着重要作用,因为它们有助于重构。没有测试套件,对系统进行重大结构更改很危险。没有容易找到错误的方法,因此在部署新代码之前,很可能将无法检测到错误,因为在新代码中查找和修复它们的成本要高得多。结果,开发人员避免在没有良好测试套件的系统中进行重构。他们尝试将每个新功能或错误修复的代码更改次数减至最少,这意味着复杂性会累积,而设计错误不会得到纠正。

有了一套很好的测试,开发人员可以在重构时更有信心,因为测试套件将发现大多数引入的错误。这鼓励开发人员对系统进行结构改进,从而获得更好的设计。单元测试特别有价值:与系统测试相比,它们提供更高的代码覆盖率,因此它们更有可能发现任何错误。

例如,在开发 Tcl 脚本语言期间,我们决定通过用字节码编译器替换 Tcl 的解释器来提高性能。这是一个巨大的变化,几乎影响了核心 Tcl 引擎的每个部分。幸运的是,Tcl 有一个出色的单元测试套件,我们在新的字节码引擎上运行了该套件。现有测试在发现新引擎中的错误方面是如此有效,以至于在字节码编译器的 alpha 版本发布之后仅出现了一个错误。

It used to be that developers rarely wrote tests. If tests were written at all, they were written by a separate QA team. However, one of the tenets of agile development is that testing should be tightly integrated with development, and programmers should write tests for their own code. This practice has now become widespread. Tests are typically divided into two kinds: unit tests and system tests. Unit tests are the ones most often written by developers. They are small and focused: each test usually validates a small section of code in a single method. Unit tests can be run in isolation, without setting up a production environment for the system. Unit tests are often run in conjunction with a test coverage tool to ensure that every line of code in the application is tested. Whenever developers write new code or modify existing code, they are responsible for updating the unit tests to maintain proper test coverage.

The second kind of test consists of system tests (sometimes called integration tests), which ensure that the different parts of an application all work together properly. They typically involve running the entire application in a production environment. System tests are more likely to be written by a separate QA or testing team.

Tests, particularly unit tests, play an important role in software design because they facilitate refactoring. Without a test suite, it’s dangerous to make major structural changes to a system. There’s no easy way to find bugs, so it’s likely that bugs will go undetected until the new code is deployed, where they are much more expensive to find and fix. As a result, developers avoid refactoring in systems without good test suites; they try to minimize the number of code changes for each new feature or bug fix, which means that complexity accumulates and design mistakes don’t get corrected.

With a good set of tests, developers can be more confident when refactoring because the test suite will find most bugs that are introduced. This encourages developers to make structural improvements to a system, which results in a better design. Unit tests are particularly valuable: they provide a higher degree of code coverage than system tests, so they are more likely to uncover any bugs.

For example, during the development of the Tcl scripting language, we decided to improve performance by replacing Tcl’s interpreter with a byte-code compiler. This was a huge change that affected almost every part of the core Tcl engine. Fortunately, Tcl had an excellent unit test suite, which we ran on the new byte-code engine. The existing tests were so effective in uncovering bugs in the new engine that only a single bug turned up after the alpha release of the byte-code compiler.