TDD与Design by Contract
原文:Test or spec? Test and spec? Test from spec!
[作者简介:Bertrand Meyer是Eiffel语言的创造者,他在著作《Object Oriented Software Construction》中提出的Design by Contract思想被视为与封装、继承和多态同样重要的OOP思想。目前,Design by Contract已经以Code Contract的形式正式进入.NET 4.0。]
Which came first? As an intermediate result towards a more general research goal, it has recently been demonstrated that the egg precedes the omelet. A full proof falls beyond the scope of this modest EiffelWorld column, but here is the idea: you can construct the omelet from the egg, but not the egg from the omelet. (The chicken is covered by a separate lemma.) The reader will already have jumped mentally to an important special case; for omelet read test, and for egg read specification, particularly in the form of Eiffel contracts.
[注译:Bertrand用鸡蛋比喻需求规范,用煎蛋卷比喻测试,意在说明测试是来源于需求规范,而反之不然。他还特别提到Design by Contract的契约是需求规范的一种形式。]
We are being told from some quarters that you can't specify anyway, and that development should be "test-driven". Eiffel programmers know from daily practice and from observation of the libraries that the first proposition is false: precise specifications in the form of contracts, even if they cover only part of the functionality, radically change the software development process. In the new scheme, tests play a critical role (see another little article of a few years back, "Qualityfirst" at: http://www.inf.ethz.ch/~meyer/publications/computer/quality_first.pdf which I believe already suggested a few of the good ideas of agile methods), but tests are not a replacement for specification: they follow from these specifications. It is indeed remarkable to see how the presence of contracts can drive the testing process. If a slogan is needed and two can do, I will venture "Contract-driven testing" and "Test-obsessed development". That works very well -- you should test all the time, with the intent of finding bugs -- but it's not a reason to drop specification and design. Specification and design are what propels both the testing process and the test cases themselves.
[注译:Bertrand不赞同开发过程由测试驱动,或者说不赞同抛开需求规范和设计,只写测试用例来表达需求。他承认测试的重要性,但测试无法取代需求规范。]
Going from specifications to test is one-way: you lose the abstractions. A specification describes the general properties of a computation, for all possible inputs; a test addresses one particular result for one particular input. From the general you can deduce the particular, but not the other way around: even a billion tests don't reveal the insight -- the abstraction -- of a specification. Omelets beget no eggs.
[注译:本段阐明了问题的本质:从需求规范到测试的过程是从抽象到具体的过程。我们可以从抽象推出具体,但无法从具体还原出抽象。]
For an Eiffel programmer, testing consists of turning on contract monitoring, exercising components, and waiting for a contract to fail. Since any test oracle can be expressed by a postcondition, but no collection of test oracles can replace a specification, we lose nothing, but we gain a great advantage over naïve (contract-less) test-driven development: retaining the fundamental abstractions that underlie our programs and their individual modules. In some later installment of this column focused more on research, I'll discuss how it is becoming possible to automate the component testing process completely, test case generation included. Suffice it for the moment to note that while we should be grateful to our extreme friends for helping to rehabilitate the much maligned process of software testing, we -- Eiffel programmers -- know something they don't seem to have realized yet: you can test from specs, but you can't spec from tests.
[注译:对于Eiffel(或其它支持Design by Contract的语言)的程序员,Bertrand提倡用代码契约来表达需求规范,因为契约没有丧失抽象性,并且同时具备了测试所具备的精确性。另外,从契约自动生成测试用例也是可能的。]
-- Bertrand Meyer
评论
需求规范的表达形式有很多种,比如:自然语言描述的文档,UML的用例图。我认为,好的需求规范形式应该具备:精确性(易于验证),抽象性(简洁,具有概况能力,易于理解),完备性(覆盖需求的各个方面)。文档和UML用例图等传统的需求规范表达形式的不足表现在:1.精确性不足;2.在开发进程中容易与实现脱钩,难于维护。TDD的测试用例是作为一种新的需求规范表达形式而提出的,它一定程度上克服了精确性和可维护性的问题,但同时却丧失了抽象性,Martin Fowler称之为Specification by Example。除此之外,Design by Contract的契约也是一种需求规范的表达形式,本文特别指出了契约同时具备抽象性和精确性的优势。不过,Bertrand没有特别强调的是:契约在表达能力即完备性方面还有所不足。因为,契约是通过无副作用的一些条件语句来表达的,并不是所有的需求规范都容易用这样的形式表达,而这恰好是TDD的优势。要让契约的威力最大化,还需要对于类的设计做一些改进,这是一个值得深入探讨的话题,比如:更多地引入函数式风格,还有采用CQRS(命令查询职责分离)设计。对于当下广大的TDD实践者来讲,本文促使我们从需求规范的角度重新审视TDD,对它的优势和不足更加明了。总之,我认为到目前为止TDD还是一种优秀的软件开发方法,值得深入地学习和实践。另外,在用支持Design by Contract的语言进行开发时,把DbC与TDD结合实现优势互补也是一种好的实践方式。