unit-testing-4.5-探索常见的测试自动化概念

2026-05-09 ⏳2.9分钟(1.2千字)

4.5 Exploring well-known test automation concepts

4.5 探索常见的测试自动化概念

仅个人学习使用,支持正版。

书名:Unit Testing: Principles, Practices, and Patterns

The four attributes of a good unit test shown earlier are foundational. All existing, well-known test automation concepts can be traced back to these four attributes. In this section, we’ll look at two such concepts: the Test Pyramid and white-box versus black-box testing.

前面介绍的优秀单元测试四个属性是基础性的。所有现有的、广为人知的测试自动化概念,都可以追溯到这四个属性。本节会看两个这样的概念:测试金字塔,以及白盒测试与黑盒测试。

4.5.1 Breaking down the Test Pyramid

4.5.1 拆解测试金字塔

The Test Pyramid is a concept that advocates for a certain ratio of different types of tests in the test suite:

测试金字塔是一种概念,它主张测试套件中不同类型测试之间应保持某种比例:

The Test Pyramid is often represented visually as a pyramid with those three types of tests in it. The width of the pyramid layers refers to the prevalence of a particular type of test in the suite. The wider the layer, the greater the test count. The height of the layer is a measure of how close these tests are to emulating the end user’s behavior. End-to-end tests are at the top—they are the closest to imitating the user experience.

测试金字塔通常以金字塔图形表示,其中包含这三类测试。金字塔各层的宽度代表某类测试在测试套件中的数量占比。层越宽,测试数量越多。层的高度表示这些测试在多大程度上模拟了最终用户行为。端到端测试位于顶部——它们最接近模拟用户体验。

Figure 4.11

Different types of tests in the pyramid make different choices in the trade-off between fast feedback and protection against regressions. Tests in higher pyramid layers favor protection against regressions, while lower layers emphasize execution speed.

金字塔中的不同测试类型,会在快速反馈和防止回归之间做出不同选择。位于金字塔较高层的测试更偏向防止回归,而较低层的测试更强调执行速度。

Figure 4.12

Notice that neither layer gives up resistance to refactoring. Naturally, end-to-end and integration tests score higher on this metric than unit tests, but only as a side effect of being more detached from the production code. Still, even unit tests should not concede resistance to refactoring. All tests should aim at producing as few false positives as possible, even when working directly with the production code. (How to do that is the topic of the next chapter.)

注意,没有任何一层会放弃抵抗重构能力。自然地,端到端测试和集成测试在这个指标上的得分高于单元测试,但这只是因为它们与生产代码距离更远所带来的副作用。即便如此,单元测试也不应该牺牲抵抗重构能力。所有测试都应该尽量少产生假阳性,即使它们直接面向生产代码工作。(如何做到这一点,是下一章的主题。)

The exact mix between types of tests will be different for each team and project. But in general, it should retain the pyramid shape: end-to-end tests should be the minority; unit tests, the majority; and integration tests somewhere in the middle.

不同团队、不同项目中,各类测试的精确比例会有所不同。但总体来说,它应该保持金字塔形状:端到端测试应该是少数,单元测试应该是多数,集成测试则位于中间。

The reason end-to-end tests are the minority is, again, the multiplication rule described in section 4.4. End-to-end tests score extremely low on the metric of fast feedback. They also lack maintainability: they tend to be larger in size and require additional effort to maintain the involved out-of-process dependencies. Thus, end-to-end tests only make sense when applied to the most critical functionality—features in which you don’t ever want to see any bugs—and only when you can’t get the same degree of protection with unit or integration tests. The use of end-to-end tests for anything else shouldn’t pass your minimum required value threshold. Unit tests are usually more balanced, and hence you normally have many more of them.

端到端测试之所以应该是少数,原因同样是 4.4 节介绍过的乘法规则。端到端测试在快速反馈指标上的得分极低。它们也缺乏可维护性:它们往往规模更大,并且需要额外精力来维护所涉及的进程外依赖。因此,端到端测试只有在用于最关键功能时才有意义——也就是那些你绝不希望看到缺陷的功能——而且只有当单元测试或集成测试无法提供同等保护程度时才应使用。把端到端测试用于其他任何场景,都不应该通过你的最低价值门槛。单元测试通常更平衡,因此你通常会拥有更多单元测试。

There are exceptions to the Test Pyramid. For example, if all your application does is basic create, read, update, and delete (CRUD) operations with very few business rules or any other complexity, your test “pyramid” will most likely look like a rectangle with an equal number of unit and integration tests and no end-to-end tests.

测试金字塔也有例外。例如,如果你的应用程序只做基本的创建、读取、更新和删除(CRUD)操作,几乎没有业务规则或其他复杂性,那么你的测试“金字塔”很可能看起来像一个矩形:单元测试和集成测试数量相等,并且没有端到端测试。

Unit tests are less useful in a setting without algorithmic or business complexity—they quickly descend into trivial tests. At the same time, integration tests retain their value—it’s still important to verify how code, however simple it is, works in integration with other subsystems, such as the database. As a result, you may end up with fewer unit tests and more integration tests. In the most trivial examples, the number of integration tests may even be greater than the number of unit tests.

在没有算法复杂度或业务复杂度的场景中,单元测试的用处较小——它们很快会退化成琐碎测试。与此同时,集成测试仍然保有价值——无论代码多简单,验证它与数据库等其他子系统集成后如何工作仍然很重要。因此,你最终可能会拥有更少单元测试和更多集成测试。在最简单的例子中,集成测试数量甚至可能超过单元测试数量。

Another exception to the Test Pyramid is an API that reaches out to a single out-of-process dependency—say, a database. Having more end-to-end tests may be a viable option for such an application. Since there’s no user interface, end-to-end tests will run reasonably fast. The maintenance costs won’t be too high, either, because you only work with the single external dependency, the database. Basically, end-to-end tests are indistinguishable from integration tests in this environment. The only thing that differs is the entry point: end-to-end tests require the application to be hosted somewhere to fully emulate the end user, while integration tests normally host the application in the same process. We’ll get back to the Test Pyramid in chapter 8, when we’ll be talking about integration testing.

测试金字塔的另一个例外,是只访问单个进程外依赖的 API,例如数据库。对于这样的应用,拥有更多端到端测试可能是可行选择。由于没有用户界面,端到端测试会运行得相当快。维护成本也不会太高,因为你只需要处理一个外部依赖,也就是数据库。基本上,在这种环境中,端到端测试和集成测试几乎无法区分。唯一不同的是入口点:端到端测试要求应用程序托管在某个地方,以完全模拟最终用户;而集成测试通常会在同一个进程中托管应用程序。第 8 章讨论集成测试时,我们会回到测试金字塔这个话题。

4.5.2 Choosing between black-box and white-box testing

4.5.2 在黑盒测试与白盒测试之间选择

The other well-known test automation concept is black-box versus white-box testing. In this section, I show when to use each of the two approaches:

另一个广为人知的测试自动化概念,是黑盒测试与白盒测试。本节会说明什么时候使用这两种方法:

There are pros and cons to both of these methods. White-box testing tends to be more thorough. By analyzing the source code, you can uncover a lot of errors that you may miss when relying solely on external specifications. On the other hand, tests resulting from white-box testing are often brittle, as they tend to tightly couple to the specific implementation of the code under test. Such tests produce many false positives and thus fall short on the metric of resistance to refactoring. They also often can’t be traced back to a behavior that is meaningful to a business person, which is a strong sign that these tests are fragile and don’t add much value. Black-box testing provides the opposite set of pros and cons.

这两种方法各有优缺点。白盒测试往往更彻底。通过分析源代码,你可以发现很多只依赖外部规格说明时可能漏掉的错误。另一方面,白盒测试产生的测试通常很脆弱,因为它们倾向于与被测代码的具体实现紧密耦合。这类测试会产生许多假阳性,因此在抵抗重构这个指标上表现不足。它们也常常无法追溯到对业务人员有意义的行为,这是测试脆弱且价值不高的强烈信号。黑盒测试则提供了相反的一组优缺点。

Table 4.1

As you may remember from section 4.4.5, you can’t compromise on resistance to refactoring: a test either possesses resistance to refactoring or it doesn’t. Therefore, choose black-box testing over white-box testing by default. Make all tests—be they unit, integration, or end-to-end—view the system as a black box and verify behavior meaningful to the problem domain. If you can’t trace a test back to a business requirement, it’s an indication of the test’s brittleness. Either restructure or delete this test; don’t let it into the suite as-is. The only exception is when the test covers utility code with high algorithmic complexity (more on this in chapter 7).

你可能还记得 4.4.5 节提到过,抵抗重构是不能妥协的:一个测试要么具备抵抗重构能力,要么不具备。因此,默认情况下应选择黑盒测试,而不是白盒测试。让所有测试——无论是单元测试、集成测试还是端到端测试——都把系统视为黑盒,并验证对问题领域有意义的行为。如果你无法把一个测试追溯到某个业务需求,那就说明这个测试可能很脆弱。要么重构这个测试,要么删除它;不要让它以原样进入测试套件。唯一例外是测试覆盖的是具有高算法复杂度的工具代码(第 7 章会进一步讨论)。

Note that even though black-box testing is preferable when writing tests, you can still use the white-box method when analyzing the tests. Use code coverage tools to see which code branches are not exercised, but then turn around and test them as if you know nothing about the code’s internal structure. Such a combination of the white-box and black-box methods works best.

注意,尽管写测试时更推荐黑盒测试,但在分析测试时,你仍然可以使用白盒方法。你可以用代码覆盖率工具查看哪些代码分支没有被执行,但随后要转过来,把它们当作你完全不了解代码内部结构一样进行测试。白盒方法和黑盒方法的这种组合效果最好。