wip-2.4
zero2.4 Integration tests in the two schools
2.4 两个学派中的集成测试
The London and classical schools also diverge in their definition of an integration test. This disagreement flows naturally from the difference in their views on the isolation issue.
伦敦学派和经典学派在集成测试的定义上也存在分歧。这种分歧自然源于它们对隔离问题的不同看法。
The London school considers any test that uses a real collaborator object an integration test. Most of the tests written in the classical style would be deemed integration tests by the London school proponents. For an example, see the tests covering the customer purchase functionality. That code is a typical unit test from the classical perspective, but it’s an integration test for a follower of the London school.
伦敦学派认为,任何使用真实协作者对象的测试都是集成测试。大多数用经典风格编写的测试,在伦敦学派支持者看来都会被视为集成测试。例如,前面覆盖客户购买功能的测试。从经典视角看,那段代码是典型的单元测试;但对伦敦学派追随者来说,它是集成测试。
In this book, I use the classical definitions of both unit and integration testing. Again, a unit test is an automated test that has the following characteristics:
在本书中,我使用经典学派对单元测试和集成测试的定义。再次说明,单元测试是一种自动化测试,具有以下特征:
- It verifies a small piece of code,
它验证一小段代码。 - Does it quickly,
它运行快速。 - And does it in an isolated manner.
并且以隔离方式运行。
Now that I’ve clarified what the first and third attributes mean, I’ll redefine them from the point of view of the classical school. A unit test is a test that:
现在我已经澄清了第一个和第三个属性的含义,接下来会从经典学派角度重新定义它们。单元测试是这样一种测试:
- Verifies a single unit of behavior,
验证单个行为单元。 - Does it quickly,
运行快速。 - And does it in isolation from other tests.
并且与其他测试隔离运行。
An integration test, then, is a test that doesn’t meet one of these criteria. For example, a test that reaches out to a shared dependency—say, a database—can’t run in isolation from other tests. A change in the database’s state introduced by one test would alter the outcome of all other tests that rely on the same database if run in parallel. You’d have to take additional steps to avoid this interference. In particular, you would have to run such tests sequentially, so that each test would wait its turn to work with the shared dependency.
那么,集成测试就是不满足这些标准之一的测试。例如,一个触达共享依赖(比如数据库)的测试,无法与其他测试隔离运行。如果并行运行,一个测试引入的数据库状态变化会改变所有依赖同一数据库的其他测试的结果。你必须采取额外步骤来避免这种干扰。尤其是,你必须顺序运行这类测试,让每个测试等待轮到自己时再使用共享依赖。
Similarly, an outreach to an out-of-process dependency makes the test slow. A call to a database adds hundreds of milliseconds, potentially up to a second, of additional execution time. Milliseconds might not seem like a big deal at first, but when your test suite grows large enough, every second counts.
类似地,触达进程外依赖会让测试变慢。一次数据库调用会增加数百毫秒,甚至可能接近一秒的额外执行时间。毫秒一开始可能看起来没什么大不了,但当测试套件增长到足够大时,每一秒都很重要。
In theory, you could write a slow test that works with in-memory objects only, but it’s not that easy to do. Communication between objects inside the same memory space is much less expensive than between separate processes. Even if the test works with hundreds of in-memory objects, the communication with them will still execute faster than a call to a database.
理论上,你可以写出一个只使用内存对象但仍然很慢的测试,但这并不容易。同一内存空间内对象之间的通信,成本远低于不同进程之间的通信。即使测试使用数百个内存对象,与它们通信仍然会比调用数据库更快。
Finally, a test is an integration test when it verifies two or more units of behavior. This is often a result of trying to optimize the test suite’s execution speed. When you have two slow tests that follow similar steps but verify different units of behavior, it might make sense to merge them into one: one test checking two similar things runs faster than two more-granular tests. But then again, the two original tests would have been integration tests already (due to them being slow), so this characteristic usually isn’t decisive.
最后,当一个测试验证两个或更多行为单元时,它也是集成测试。这通常是试图优化测试套件执行速度的结果。当你有两个慢测试,它们遵循相似步骤但验证不同的行为单元时,把它们合并成一个测试可能是合理的:一个检查两个相似事项的测试,会比两个更细粒度的测试运行得更快。但话说回来,原来的两个测试已经是集成测试了(因为它们很慢),所以这个特征通常不是决定性的。
An integration test can also verify how two or more modules developed by separate teams work together. This also falls into the third bucket of tests that verify multiple units of behavior at once. But again, because such an integration normally requires an out-of-process dependency, the test will fail to meet all three criteria, not just one.
集成测试也可以验证由不同团队开发的两个或更多模块如何协同工作。这也属于第三类:一次验证多个行为单元的测试。但同样,由于这种集成通常需要进程外依赖,测试往往会同时不满足三个标准,而不仅仅是不满足其中一个。
Integration testing plays a significant part in contributing to software quality by verifying the system as a whole. I write about integration testing in detail in part 3.
集成测试通过把系统作为一个整体来验证,对软件质量有重要贡献。我会在第 3 部分详细讨论集成测试。
2.4.1 End-to-end tests are a subset of integration tests
2.4.1 端到端测试是集成测试的子集
In short, an integration test is a test that verifies that your code works in integration with shared dependencies, out-of-process dependencies, or code developed by other teams in the organization. There’s also a separate notion of an end-to-end test. End-to-end tests are a subset of integration tests. They, too, check to see how your code works with out-of-process dependencies. The difference between an end-to-end test and an integration test is that end-to-end tests usually include more of such dependencies.
简而言之,集成测试验证你的代码能否与共享依赖、进程外依赖,或组织中其他团队开发的代码集成工作。还有一个单独的概念叫端到端测试。端到端测试是集成测试的子集。它们同样检查你的代码如何与进程外依赖一起工作。端到端测试和集成测试之间的区别在于:端到端测试通常包含更多这样的依赖。
The line is blurred at times, but in general, an integration test works with only one or two out-of-process dependencies. On the other hand, an end-to-end test works with all out-of-process dependencies, or with the vast majority of them. Hence the name end-to-end, which means the test verifies the system from the end user’s point of view, including all the external applications this system integrates with.
这条界线有时比较模糊,但总体来说,集成测试只使用一两个进程外依赖。另一方面,端到端测试会使用所有进程外依赖,或其中绝大多数。因此它叫端到端,意思是测试从最终用户视角验证系统,包括系统集成的所有外部应用。
People also use such terms as UI tests (UI stands for user interface), GUI tests (GUI is graphical user interface), and functional tests. The terminology is ill-defined, but in general, these terms are all synonyms.
人们还会使用 UI 测试(UI 指用户界面)、GUI 测试(GUI 指图形用户界面)和功能测试等术语。这些术语定义并不严格,但总体上它们都是同义词。
Let’s say your application works with three out-of-process dependencies: a database, the file system, and a payment gateway. A typical integration test would include only the database and file system in scope and use a test double to replace the payment gateway. That’s because you have full control over the database and file system, and thus can easily bring them to the required state in tests, whereas you don’t have the same degree of control over the payment gateway.
假设你的应用使用三个进程外依赖:数据库、文件系统和支付网关。一个典型集成测试只会把数据库和文件系统纳入范围,并用测试替身替换支付网关。这是因为你对数据库和文件系统拥有完全控制,因此可以很容易地在测试中把它们置于所需状态;但你对支付网关没有同等程度的控制。
With the payment gateway, you may need to contact the payment processor organization to set up a special test account. You might also need to check that account from time to time to manually clean up all the payment charges left over from the past test executions.
对于支付网关,你可能需要联系支付处理机构来设置一个特殊测试账号。你还可能需要不时检查该账号,手动清理过去测试执行留下的所有支付费用。
Since end-to-end tests are the most expensive in terms of maintenance, it’s better to run them late in the build process, after all the unit and integration tests have passed. You may possibly even run them only on the build server, not on individual developers’ machines.
由于端到端测试在维护方面成本最高,最好在构建流程后期运行它们,也就是在所有单元测试和集成测试都通过之后再运行。你甚至可以只在构建服务器上运行它们,而不在单个开发者机器上运行。
Keep in mind that even with end-to-end tests, you might not be able to tackle all of the out-of-process dependencies. There may be no test version of some dependencies, or it may be impossible to bring those dependencies to the required state automatically. So you may still need to use a test double, reinforcing the fact that there isn’t a distinct line between integration and end-to-end tests.
请记住,即使使用端到端测试,你也可能无法处理所有进程外依赖。有些依赖可能没有测试版本,或者无法自动把这些依赖置于所需状态。因此,你可能仍然需要使用测试替身,这进一步说明集成测试和端到端测试之间并没有清晰界线。