与测试驱动开发相比,为什么按合同设计不那么受欢迎?

时间:2009-01-26 20:57:44

标签: unit-testing tdd design-by-contract

您可能认为这个问题就像之前在StackOverflow上提出的this问题一样。但我试图以不同的方式看待事物。

在TDD中,我们编写包含不同条件,标准,验证码的测试。如果一个班级通过了所有这些测试,我们很高兴。这是一种确保班级实际上做了它应该做的事情的方式,而不是别的。

如果您逐字逐句地遵循 Bertrand Meyers '预订面向对象的软件构建,那么这个类本身就有内部和外部的契约,所以它只做它应该做的事情什么也没做。不需要进行外部测试,因为确保合同的代码是该类的一部分。

快速举例说明事项

  

TDD

     
      
  1. 创建测试以确保在所有情况下的值范围为(0-100)

  2.   
  3. 创建一个包含传递测试的方法的类。

  4.         

    DBC

         
        
    1. 创建一个类,为该成员var创建一个范围为(0-100)的合同,设置合同违约合同,定义方法。
    2.   

我个人喜欢DBC方法。


有没有理由说纯DBC不那么受欢迎?它是语言或工具还是敏捷,还是我喜欢让代码对自己负责?

如果你认为我的想法不对,我会非常愿意学习。

9 个答案:

答案 0 :(得分:29)

DBC的主要问题是,在绝大多数情况下,合同无法正式指定(至少不方便),或者无法使用当前的静态分析工具进行检查。在我们超越主流语言(不是埃菲尔)的这一点之前,DBC不会给出人们需要的那种保证。

在TDD中,测试是由人类根据当前的方法自然文本规范编写的(希望有充分记录)。因此,人类通过编写测试来解释正确性,并根据该解释得到一些保证。

如果您阅读Sun编写JavaDocs的指南,它说文档应该基本上制定一份足以编写测试计划的合同。因此,合同设计不一定与TDD互斥。

答案 1 :(得分:23)

TDD和DbC是两种不同的策略。 DbC允许在运行时快速失败,而TDD在“编译时”行动(确切地说,它在编译后立即添加新步骤以运行单元测试)。

这是TDD相对于DbC的一大优势:它可以获得更早的反馈。当您以TDD方式编写代码时,您可以同时获得代码及其单元测试,您可以根据您认为应该在测试中编码的内容验证它是否“有效”。使用DbC,您可以获得嵌入式测试的代码,但您仍需要练习它。 IMO,这肯定是Dbc不那么受欢迎的原因之一。

其他优势:TDD创建了一个自动测试套件,可以检测(预防读取)回归并使Refactoring安全,这样您就可以逐步增加设计。 DbC不提供这种可能性。

现在,使用DbC快速失败可能非常有用,特别是当您的代码与其他组件接口或必须依赖外部源时,在这种情况下测试合同可以节省您的时间。

答案 2 :(得分:14)

首先,我是埃菲尔软件工程师,所以我可以从经验谈谈这个问题。


TDD与DbC 的前提是不正确的

这两种技术互不矛盾,但相互补充。补充与断言和目的的放置有关。

TDD的目的既有组件也有范围。 TDD的基本组件是布尔断言和对象特征(例如方法)执行。步骤很简单:

  1. 创建一个对象。
  2. 在功能中执行一些代码。
  3. 对对象的数据状态进行断言。
  4. 失败的断言,未通过测试。传递所有断言是目标。

    与TDD一样,“按合同设计”的合同具有目的,范围和组成部分。虽然TDD仅限于单位测试时间,但合同可以贯穿整个SDLC(软件开发生命周期)!在TDD的范围内,对象方法(特征)的执行将执行合同。在Eiffel Studio自动测试(TDD)设置中,一个人创建一个对象,进行调用(就像其他语言中的TDD一样),但这里是相似的结束。

    在带有自动测试的Eiffel Studio和带有合同的Eiffel代码中,目的会有所改变。我们想测试客户 - 供应商关系。我们的TDD代码假装是其对象的供应商方法的客户。我们创建我们的对象并基于此目的调用方法,而不仅仅是简单的" TDD-ish方法测试"。因为对我们的方法(特征)的调用具有契约,所以这些契约将作为我们在自动测试中的TDD-ish代码的一部分执行。因为这是真的,我们在代码中放置的契约断言(测试)不必出现在我们的TDD测试代码中。我们的工作(作为程序员)只是确保:A)代码+契约沿着所有N路径执行,B)代码+契约使用所有合理的数据类型和范围执行。

    或许还有更多关于TDD-DbC补充关系的文章,但在这件事上我不会对你粗暴。我只想说TDD和DbC与其他人没有分歧 - 不是远远不够!

    DbC合同的权力超出TDD可达到的范围

    现在,我们可以将注意力转移到TDD可以达到的合同设计合同的权力之上!

    合同中存在合同。它们不是外部的,而是内部的。关于合同的最强大的一点(超出客户 - 供应商合同关系的基础)是编译器旨在了解它们!它们不是埃菲尔的附加功能!因此,他们参与继承的每个方面(传统的垂直是 - 遗传和横向或横向泛型)。而且,他们到达了TDD无法到达的地方 - 方法(特征)。

    虽然TDD可以轻松模仿前置条件和后置条件,但TDD无法到达代码内部并执行循环不变合同,也无法定期进行抽样检查"检查"在执行时沿着代码块收缩。这是一个强大的逻辑和定性范式,以及关于按合同设计如何运作的现实。

    此外,TDD不能以最微弱的方式进行类不变量。我已经尽力让我的自动测试代码(实际上只是应用TDD的Eiffel Studios版本)来做类不变的模仿。这不可能。要理解为什么你必须知道埃菲尔铁塔不变量如何工作的原因和结果。所以,目前,你只需要接受我(或不是)TDD无法执行此任务的话,即DbC处理如此轻松,优秀和优雅!

    DbC的范围并未以上述概念结束

    我们在上面指出,TDD生活在单元测试时间。合同,因为它们在编译器的监督和控制下应用于代码中,适用于代码可以执行的任何地方:

    1. Workbench:作为程序员,您正在使用代码查看它是否有效(例如在TDD时间之前或与TDD时间一起使用)。

    2. 单元测试:您的持续集成测试,单元测试,TDD等。

    3. Alpha测试:您的初始测试用户在运行可执行文件时会跳过合同

    4. Beta测试:更广泛的用户群也会绊倒合同。

    5. 生产:最终的可执行文件(或生产系统)可以通过合同进行连续测试(TDD不能)。

    6. 在上述每种情况中,都会发现一个人可以控制哪些合同运行以及来自哪些来源!您可以选择性地,细致地打开和关闭各种形式的合同和控制,在编译器应用的时间和地点极其精确!

      如果所有这些还不够,合同(按设计)可以做一些TDD断言无法做到的事情:告诉你调用堆栈中的哪个位置以及哪个客户 - 供应商关系被破坏,以及为什么(也会立即建议如何解决)。为什么这是真的?

      TDD断言旨在告诉您事后代码运行(执行)的结果。 TDD断言只能看到被检查方法的当前状态。 TDD断言不能从他们在代码库外部的位置做出来的,就是要准确地告诉你哪个调用失败了,为什么!你看 - 你对某种方法的初始TDD调用将触发该方法。很多时候,这个方法会调用另一个,另一个,另一个 - 有时候,当调用堆栈上下晃动时,有一个破坏:有些东西写错了数据,根本没写,或者写它什么时候不应该。

      TDD就像警察在谋杀事件发生后出现在犯罪现场一样。他们所留下的只是法医线索,他们希望这些线索会引导他们成为嫌疑人和信徒。但是,如果犯罪发生的话,我们可以在那里怎么办?这是TDD断言的放置与合同断言之间的区别。合同是为了捕捉正在进行的犯罪,他们直接指向罪犯,因为它正在犯罪!

      小结

      让我们回顾一下。

      • TDD与DbC并不矛盾。

      • 它是一套补充和一套合作的技术,但具有不同的功能和用途,以及与之配合使用的工具。

      • 合同进一步深入,并在您的代码中断时显示更多信息。

      • TDD是执行合同的催化剂之一。

      在一天结束时:我想要两个!阅读完所有内容后(如果幸存下来),我希望你也能这样做。

答案 3 :(得分:12)

按合同设计和测试驱动的开发并不是互相排斥的

Bertrand Meyer的书面向对象的软件构建,第2版并没有说你永远不会犯错误。实际上,如果你看一下“当合同被破坏”这一章时,它会讨论当一个函数未能完成其合同所述的情况时会发生什么。

使用DbC技术这一简单事实并不能使您的代码正确无误。按合同设计以合同的形式为您的代码及其用户建立明确定义的规则。这很有帮助,但无论如何你总是可以把事搞得一团糟,只有你早先可能会注意到它。

测试驱动的开发将从外部世界检查黑盒子样式,即你的类的公共接口行为正确。

答案 4 :(得分:7)

我认为最好同时使用这两种方法而不是一种方法。

在我看来,在课堂上完全执行合同及其方法本身似乎是不切实际的。

例如,如果一个函数说它会通过某种方法对字符串进行散列并将散列字符串作为输出返回,那么该函数如何强制该字符串被正确散列?再次哈希,看看它们是否匹配?看起来很傻。反转哈希以查看您是否获得原始哈希?不可能。相反,您需要一组测试用例来确保函数正常运行。

另一方面,如果您的特定实现要求您的输入数据具有一定的大小,那么建立合同并在您的代码中强制执行它似乎是最好的方法。

答案 5 :(得分:5)

在我看来,TDD更具“归纳感”。您从示例(测试用例)开始,您的代码体现了这些示例的一般解决方案。

DBC似乎更具“演绎性”,在收集了您确定对象行为和合同的要求之后。然后,您可以对这些合同的具体实施进行编码。

编写合同有点困难,比具体行为示例的测试更为困难,这可能是TDD比DBC更受欢迎的部分原因。

答案 6 :(得分:5)

我过去曾使用过两者,发现 DBC风格较少"侵入性" 。 DBC的驱动程序可能是常规应用程序运行。对于单元测试,您必须注意设置,因为您期望(验证)一些响应。对于DBC,你不必这样做。规则以数据无关的方式编写,因此无需设置和模拟。

更多关于我使用DBC / Python的经验:http://blog.aplikacja.info/2012/04/classic-testing-vs-design-by-contract/

答案 7 :(得分:4)

我认为没有理由说两者都不能共存。看一下这个方法很精彩,一眼就知道合同究竟是什么。知道我可以运行我的单元测试并且知道我的最后一次更改没有任何损坏,这也很棒。这两种技术并不相互排斥。为什么合同设计不是更受欢迎是一个谜。

答案 8 :(得分:4)

我将“按合同设计”视为所有情况下成功/失败的规范,而测试驱动开发则针对一个特定情况。如果TDD案例成功,则假定某个函数正在执行它的工作,但它没有考虑可能导致其失败的其他情况。

另一方面,

按合同设计并不一定能保证所需的答案,只有答案是“正确的”。例如,如果函数返回应该返回一个非空字符串,那么您在ENSURE中唯一可以假设的是它不会为空。

但也许它不会返回预期的字符串。合同无法确定只有一个测试可以证明它是按照规范执行的。

所以这两者是互补的。

Greg