[翻译] Robert C. Martin谈TDD

Robert C. Martin

范德成 译

Q.: What’s TDD?

A.: It’s a software development technique used by programmers to incrementally design their system.

问:TDD(测试驱动开发)是什么?

答:它是一种软件开发技术,程序员用它来实现增量式软件系统设计。

Q.: What does it look like?

A.: Programmers break their work down into small chunks, or units. For each small chunk, they write a small test that provides an example of how that code should behave. They then write a small chunk of code that makes the test pass. Once they get the test passing, they make sure they clean up any design or coding deficiencies that they just introduced in the small chunk of code.

问:它使用起来是什么样子的?

答:程序员把他们的工作分成多个小段,或称为单元。对于每个小段,他们编写一个小的测试用例,其提供了生产代码该有怎样行为的一个例子。然后,他们再编写一小段生产代码,让测试通过。一旦他们让测试通过了,他们就能确信已经清除了该小段代码上刚刚引入的设计或编码上的缺陷(已被该测试覆盖的场景下的缺陷——范德成注)。

Q.: So they’re just writing unit tests?

A.: They are using a unit test framework to help specify how every small bit of added code should behave. So yes, they’re writing unit tests, but the tests also double as documentation on what the system does.

问:所以说,他们仅仅是在写单元测试而已?

答:他们用一个单元测试框架来帮助指定每一小段新增的代码该有怎样的行为。所以,是的,他们是在编写单元测试,但这些测试同时也扮演着描述系统行为的文档的角色。

Q.: So they’re just writing unit tests.

A.: They are using unit tests as a way of incrementally designing the system. The tests get written first.

问:所以说,他们还只是在写单元测试而已。

答:单元测试是他们用于增量式系统设计的方法。测试是先于生产代码编写的。

Q.: I’m not seeing how this is any different from unit testing. So what if you write the tests first?

A.: You get different results. Writing tests after doesn’t change anything. They might help the programmer find a few problems, but unit testing alone don’t otherwise compel programmers to change the design and quality of the codebase.

问:我并没有觉得这和事后编写单元测试有何不同。先写测试的区别是什么呢?

答:你将得到不同的结果。先写生产代码再写测试的话,不会给你带来任何改变。它们也许能为程序员找到一些问题,但只是编写单元测试的话,将不会促使程序员改进代码的设计和质量。

Q.: But TDD does change the design and quality?

A.: A few studies (see Research on TDD, on page 303) demonstrate that there are fewer defects when applying TDD. Further, doing TDD allows programmers to continually change the code and design for the better.

问:但TDD能为设计和质量带来改变?

答:有研究(参见Research on TDD一文的第303页)表明,当应用TDD时,缺陷会变得更少。而且,用TDD能让程序员持续地改进代码和设计。

Q.: How does TDD allow programmers to change the code any more than they did before?

A.: Every small chunk of code a programmer adds to the system comes into existence only once a test exists to demonstrate its behavior. That means all of the code in the system gets tested. Having tests for everything means that a programmer can make constant small changes as needed to keep the code clean. Without sufficient tests, the old adage of “If it ain’t broke, don’t fix it” is reality.

问:TDD怎样帮助程序员更容易地改变之前编写的代码?

答:程序员加入到系统中的每一小段生产代码都只有在能演示其行为的测试存在之后才会出现。这意味着系统中所有的代码都会被测试。对所有东西都有测试意味着程序员能为了保持代码整洁而持续地做小的修改。没有足够的测试的话,我们这行俗话说的“如果它没有挂,那就不要修复它”就将成为现实。

Q.: So what? Can’t we save time by not worrying about how clean the code is?

A.: Some studies show that 80 percent of software efforts are expended on maintaining (not fixing) the software. In a codebase growing to a large size over time, things that might have taken a couple hours to accomplish can bloat to taking several days. Even a simple question—“What does the software do in this case?”—can require a several-hour foray into a complex codebase on the part of the programmer.

问:这又怎样?如果我们不去关心代码整洁不整洁,不就可以节省时间了么?

答:有研究显示80%的软件上的工作是花费在维护软件(而不是修正bug)上面。在一个随着时间的推移变得越来越大的代码库中,原本可能只需要花几个小时就能完成的事情可能会变得需要花费几天来做。就算是一个简单的问题——“在这种情况下,软件将会做什么?”——都可能需要程序员深入一个复杂的代码库几个小时才能回答。

Q.: Doesn’t that say something about the quality of programmers? Can’t they just write clean code in the first place?

A.: Think of programming like writing. Even good writers continually rework their sentences and paragraphs to improve understanding. And they still get presented with numerous problems by editors who review their work. It’s enough of a challenge to write code that simply provides expected behaviors, so step 1 is to put the correct code in place. Step 2 is to improve the code by making sure it’s part of a maintainable design.

问:那不是因为程序员的水平问题吗?难道他们不能一上来就编写出整洁的代码吗?

答:让我把编程比喻成写作吧。即使是优秀的作者都会持续地改进他们的语句和段落,以增进读者的理解。就算如此,他们还是会面对复审他们作品的编辑们提出的各种问题。仅仅写出能提供预期行为的代码就已经够有挑战了,所以,第一步是让正确的代码就位;第二步是改进它,以保证它属于一种可维护的设计。

Q.: I’m still not understanding why you couldn’t achieve the same results with just writing a few unit tests after the code gets completed.

A.: For human reasons, it doesn’t happen. First, once most programmers write the code, they think they’re done with the “real” work. They have high confidence in their capability to write the correct code and are often satisfied by a simple manual test or two. So, they have little interest in writing additional unit tests to prove what they already know. They also feel they crafted the code well enough and less frequently take advantage of the tests to clean up their code. Programmers, in general, do as little as needed to get past what they view as management mandates. Second, time schedule pressures often dominate. Anything done after the fact—after the code gets built—gets short shrift.

问:我仍旧不是很明白,为什么在代码完成后加一些单元测试就不能取得同样的效果。

答:由于人性的原因,这是会不一样的。首先,一旦大多数程序员写完生产代码了,他们就认为已经把“实际的”工作完成了。他们对他们编写正确的代码的能力有着很高的信心,并且在做一两次手工测试后就认为已经足够了。所以,他们很少有兴趣编写额外的单元测试来验证他们认为“已完成”的代码。他们也觉得他们已经把代码写得足够好了,因此也较少用单元测试来辅助代码的整理工作。总的来说,为了实现管理层要求的东西,程序员想把要做的事情变得越少越好。其次,时间计划上的压力通常很大。任何在“实物”完成后的事情,也就是生产代码写完后的事情,通常会被轻视。

Q.: Shouldn’t we allow programmers to use their own professional judgment about what code should be tested? Isn’t some code so simple that it doesn’t really need that level of testing? Aren’t they wasting time by this rigid insistence on testing everything?

A.: Most systems don’t really have all that much code that’s so simple it can’t break, so there’s not much testing effort to be saved here. I’m also no longer surprised by how many defects programmers uncover in areas of the system that look innocuous and perfect. Defects are very costly in many ways, and having fewer by virtue of practicing TDD provides great savings. You’ll also waste far less time on the seemingly simple challenge of figuring out how the existing system behaves. Every programmer who’s gone back and tried to write unit tests against a large, existing codebase reports that it’s very difficult. The primary reason is that the codebase wasn’t structured with testing in mind, and as a result it’s much harder to hook tests into the system. The typical programmer punts when faced with such challenges.

问:为什么不让程序员凭借自己的专业判断力来决定哪些代码应当被测试呢?是不是有些代码非常简单,以至于并不真的需要那种程度的测试呢?为了满足这种僵化的“测试所有东西”的要求,难道他们不是在浪费时间吗?

答:大多数系统并不包含多少简单的“不可能出错”的代码,所以,这样做并不能节省很多测试工作量。同时,当我看到程序员在系统的某些看似完美的功能上发现很多缺陷时,我也不再感到惊讶。缺陷在很多方面都很消耗资源,因此,通过TDD的实践来减少缺陷,能够获得更多的收益。在“找出现有系统的行为”这一看似简单实则有挑战的问题上,你也会节省很多时间。为已有的大型代码库新编写过单元测试的每个程序员都表示,这件事很难,其主要原因在于该代码库的构造过程并没有带有“可测试”的思路,因此,要为该系统中加入测试将变得困难得多。多数程序员面临这种事情时,通常会抱怨。

Q.: I’ve heard some programmers say that you really only need 70 percent of your system unit-tested and that you can cover the rest with functional tests.

A.: Are you comfortable with 30 percent of your system not being covered with tests that provide fast feedback? If there are defects in that near-third of your system, you’ll find out about them much later. If you need to rework that portion of the system to accommodate new features, it will be a considerably slower effort. You’ll either need to add unit tests (which is harder with existing code) or use slower functional tests to make sure you don’t break things.

问:我听说有些程序员说,实际上你只需要用单元测试来覆盖系统的70%就行,剩下的可以用功能测试来覆盖。

答:你对“你系统30%的部分没有被能提供快速反馈的测试所覆盖”这一事实感到舒服吗?如果在你系统的这30%中有缺陷的话,你将会很晚才发现它们。如果你需要重整系统的这一部分来加入新功能,这会变得慢得多,因为你要么添加单元测试(而在已有代码上加单元测试是更困难的),或者使用更慢的功能测试来保证你不会破坏别的东西。

Q.: Having fast tests for all logic makes sense. But don’t you end up with a lot more code, in the form of tests, that you have to maintain?

A.: No. Most systems are probably double the size they need to be, if not more. Part of that is a by-product of the inability to safely remove redundant chunks of code as programmers add them. Anecdotal evidence suggests that yes, the amount of unit testing code might be equivalent, or even a little larger than, a production codebase. But a codebase created entirely by following TDD will likely be half the size. So, it comes out in the wash, plus you get all of the other side benefits of TDD.

问:为所有逻辑都编写快速的测试用例是有意义的,但是,这样一来,你难道不会留下好多测试代码吗?这些代码也是需要维护的呀。

答:不。大多数软件系统的规模通常都是它们实际所需要成为的大小的两倍或更多。之所以会这样,部分原因就是当程序员添加新代码时,无法安全地移除冗余的代码。是的,坊间留传的说法是单元测试代码的量会和生产代码的量差不多,甚至更多些。但是,遵循TDD过程全新创建出来的代码库通常只有不使用TDD的代码库的一半大小。所以,多出来的代码在清理的过程中消失了一大部分,同时你又拥有了TDD所带来的各种好处。

Q.: I’ve heard that TDD doesn’t really catch a lot of bugs. Doesn’t this suggest that it’s all a waste of time?

A.: TDD prevents you from integrating defects into the system in the first place. You always write a test, you always get it to pass, and if it doesn’t pass, you fix it before committing the code. It seems like a better approach than checking in code and finding out only much later that it doesn’t work or breaks something else.

问:我听说实际上TDD并不会捕捉很多bug。这难道不表示它是在浪费时间吗?

答:TDD在早期就避免你把缺陷集成进系统中。你总是得编写一个测试,总是要让它通过,如果它不通过,你就在提交代码前把它修好。你可以先提交代码,而后过了好久发现它不工作,或者破坏了其他东西,但相比之下,还是TDD的做法看上去更好些吧?

Q.: You’re making TDD sound like it’s a silver bullet.

A.: TDD is a great tool, but it is only one tool in what needs to be a good-sized toolbox. TDD is insufficient. Proving that small units of code work in isolation doesn’t prove that you can string them together in order to produce a desired functional need. You need also acceptance tests, which might also include performance tests, load tests, other forms of integration tests, and perhaps some level of exploratory testing.

问:你把TDD说得像是个银弹似的。

答:TDD是一个好用的工具,但它也只是软件工程“工具箱”里的一个工具而已。仅仅是证明一个个小单元的代码在隔离的情况下能工作,并不能证明它们串联起来能提供预期的功能需求。你同时也需要验收测试(acceptance test),其中也可能包括性能测试、负载测试、其他形式的集成测试等,还可能包含某种程度的探索式测试(exploratory testing)。

返回“编程天地”