by kevin
11.
十月 2022 13:41
>
At Google, we’ve found that engineers sometimes need to be persuaded that testing via public APIs is better than testing against implementation details. The reluctance is understandable because it’s often much easier to write tests focused on the piece of code you just wrote rather than figuring out how that code affects the system as a whole. Nevertheless, we have found it valuable to encourage such practices, as the extra upfront effort pays for itself many times over in reduced maintenance burden. Testing against public APIs won’t completely prevent brittleness, but it’s the most important thing you can do to ensure that your tests fail only in the event of meaningful changes to your system.(在谷歌,我们发现工程师有时需要被说服,通过公共API进行测试比针对实现细节进行测试要好。这种不情愿的态度是可以理解的,因为写测试的重点往往是你刚刚写的那段代码,而不是弄清楚这段代码是如何影响整个系统的。然而,我们发现鼓励这种做法是很有价值的,因为额外的前期努力在减少维护负担方面得到了许多倍的回报。针对公共API的测试并不能完全防止脆性,但这是你能做的最重要的事情,以确保你的测试只在系统发生有意义的变化时才失败。)
The most common reason for problematic interaction tests is an over reliance on mocking frameworks. These frameworks make it easy to create test doubles that record and verify every call made against them, and to use those doubles in place of real objects in tests. This strategy leads directly to brittle interaction tests, and so we tend to prefer the use of real objects in favor of mocked objects, as long as the real objects are fast and deterministic.(交互测试出现问题的最常见原因是过度依赖mocking框架。这些框架可以很容易地创建测试替换,记录并验证针对它们的每个调用,并在测试中使用这些替换来代替真实对象。这种策略直接导致了脆弱的交互测试,因此我们倾向于使用真实对象而不是模拟对象,只要真实对象是快速和确定的。)
The problem is that framing tests around methods can naturally encourage unclear tests because a single method often does a few different things under the hood and might have several tricky edge and corner cases. There’s a better way: rather than writing a test for each method, write a test for each behavior.[^4] A behavior is any guarantee that a system makes about how it will respond to a series of inputs while in a particular state.[^5] Behaviors can often be expressed using the words “given,” “when,” and “then”: “Given that a bank account is empty, when attempting to withdraw money from it, then the transaction is rejected.” The mapping between methods and behaviors is many-to-many: most nontrivial methods implement multiple behaviors, and some behaviors rely on the interaction of multiple methods. (问题是,围绕方法测试框架自然会鼓励不清晰测试,因为单个方法经常在背后下做一些不同的事情,可能有几个棘手的边缘和角落的情况。有一个更好的方法:与其为每个方法写一个测试,不如为每个行为写一个测试。 行为是一个系统对它在特定状态下如何响应一系列输入的任何保证。"鉴于一个银行账户是空的,当试图从该账户中取钱时,该交易被拒绝。" 方法和行为之间的映射是多对多的:大多数不重要的方法实现了多个行为,一些行为依赖于多个方法的交互。)
If humans are bad at spotting bugs from string concatenation, we’re even worse at spotting bugs that come from more sophisticated programming constructs like loops and conditionals. The lesson is clear: in test code, stick to straight-line code over clever logic, and consider tolerating some duplication when it makes the test more descriptive and meaningful. We’ll discuss ideas around duplication and code sharing later in this chapter.(如果人类不善于发现来自字符串连接的错误,那么我们更不善于发现来自更复杂的编程结构的错误,如循环和条件。这个教训很清晰:在测试代码中,坚持使用直线代码而不是复杂的逻辑,并在测试更具描述性的时候考虑容忍一些重复。我们将在本章后面讨论关于重复和代码共享的想法。)