你可能会觉得这个问题是像这样的问题在计算器上问早.但我试图以不同的方式看待事物.
在TDD中,我们编写包含不同条件,标准,验证码的测试.如果一个班级通过了所有这些测试,我们很高兴.这是一种确保班级实际上做了它应该做的事情而不是别的事情的方法.
如果你按照Bertrand Meyers的书中逐字逐句地介绍面向对象的软件构建,那么这个类本身就有内部和外部的契约,所以它只能做它应该做的事情,而不是别的.不需要进行外部测试,因为确保合同的代码是类的一部分.
快速举例说明事情
TDD
创建测试以确保在所有情况下的值范围为(0-100)
创建一个包含传递测试的方法的类.
DBC
创建一个类,为该成员创建一个合同
var
范围从(0-100),设置合同违约合同,定义一个方法.
我个人喜欢DBC方法.
有没有理由说纯DBC不那么受欢迎?它是语言或工具还是敏捷,还是我喜欢让代码对自己负责?
如果你认为我思考不对,我会更愿意学习.
DBC的主要问题是,在绝大多数情况下,合同无法正式指定(至少不方便),或者无法使用当前的静态分析工具进行检查.在我们超越主流语言(不是埃菲尔)的这一点之前,DBC不会给出人们需要的那种保证.
在TDD中,测试是由人类根据当前的方法自然文本规范编写的(希望)有充分的文档记录.因此,人类通过编写测试来解释正确性,并基于该解释获得一些保证.
如果您阅读Sun编写JavaDocs的指南,它会说文档应该基本上制定一份足以编写测试计划的合同.因此,合同设计不一定与TDD互斥.
TDD和DbC是两种不同的策略.DbC允许在运行时快速失败,而TDD在" 编译时 "执行(确切地说,它在编译后立即添加新步骤以运行单元测试).
这是TDD相对于DbC的一大优势:它可以获得更早的反馈.当您以TDD方式编写代码时,您可以同时获得代码及其单元测试,您可以根据您认为应该在测试中编码的内容验证它是否"有效".使用DbC,您可以获得嵌入式测试的代码,但您仍需要练习它.IMO,这肯定是Dbc不那么受欢迎的原因之一.
其他优点:TDD创建了一个自动测试套件,允许检测(读取防止)回归并使重构安全,以便您可以逐步增加设计.DbC不提供这种可能性.
现在,使用DbC快速失败可能非常有用,特别是当您的代码与其他组件接口或必须依赖外部源时,在这种情况下测试合同可以节省您的时间.
首先,我是埃菲尔软件工程师,所以我可以从经验中谈谈这件事.
这两种技术互不矛盾,但相互补充.补充与断言和目的的放置有关.
TDD的目的既有组件也有范围.TDD的基本组件是布尔断言和对象特征(例如方法)执行.步骤很简单:
创建一个对象.
在功能中执行一些代码.
对关于对象的数据状态进行断言.
失败的断言,测试失败.传递所有断言是目标.
与TDD一样,"按合同设计"的合同具有目的,范围和组成部分.虽然TDD仅限于单位测试时间,但合同可以贯穿整个SDLC(软件开发生命周期)!在TDD的范围内,对象方法(特征)的执行将执行合同.在Eiffel Studio自动测试(TDD)设置中,创建一个对象,进行调用(就像其他语言中的TDD一样),但这里是相似性结束的地方.
在带有自动测试的Eiffel Studio和带有合同的Eiffel代码中,目的会有所改变.我们想测试客户 - 供应商关系.我们的TDD代码假装是其对象的供应商方法的客户.我们创建对象并基于此目的调用方法,而不仅仅是简单的"TDD-ish方法测试".因为对我们的方法(特征)的调用具有契约,所以这些契约将作为我们在自动测试中的TDD-ish代码的一部分执行.因为这是真的,我们在代码中放置的契约断言(测试)不必出现在我们的TDD测试代码中.我们的工作(作为程序员)只是确保:A)代码+契约沿着所有N路径执行,B)代码+契约使用所有合理的数据类型和范围执行.
或许还有更多关于TDD-DbC补充关系的文章,但在这件事上我不会对你粗鲁.我只想说TDD和DbC与其他人并不矛盾 - 不是长枪!
现在,我们可以将注意力转移到超出TDD可达范围的合同设计合同的力量!
合同存在于代码中.它们不是外部的,而是内部的.关于合同的最强大的一点(超出客户 - 供应商合同关系的基础)是编译器旨在了解它们!它们不是埃菲尔的附加功能!因此,他们参与继承的每个方面(传统的垂直是 - 遗传和横向或横向泛型).而且,它们到达方法(特征)内部TDD无法到达的地方.
虽然TDD可以轻松地模拟前置条件和后置条件,但TDD无法到达代码内部并执行循环不变合同,也无法在执行时沿着代码块定期检查"检查"合同.这是一个强大的逻辑和定性范式,以及关于合同设计如何运作的现实.
此外,TDD不能以最微弱的方式进行类不变量.我已经尽力让我的自动测试代码(实际上只是应用TDD的Eiffel Studios版本)来做类不变的模仿.这不可能.要理解为什么你必须知道埃菲尔铁塔不变量如何工作的进出.所以,目前,你只需要接受我(或不是)TDD无法执行此任务的话,DbC可以轻松,优秀地处理!
我们在上面指出TDD生活在单元测试时间.合同,因为它们在编译器的监督和控制下应用于代码中,适用于可以执行代码的任何地方:
Workbench:作为程序员,您正在使用代码来查看它的工作情况(例如在TDD时间之前或与TDD时间一起使用).
单元测试:您的持续集成测试,单元测试,TDD等.
Alpha测试:您的初始测试用户在运行可执行文件时会跳过合同
Beta测试:更广泛的用户群也将绊倒合同.
生产:最终的可执行文件(或生产系统)可以通过合同进行连续测试(TDD不能).
在上面的每种情况中,人们会发现一个人可以控制哪些合同运行以及来自哪些来源!您可以选择性地,精细地打开和关闭各种形式的合同和控制,在编译器应用的时间和地点极其精确!
如果所有这些还不够,合同(按设计)可以做一些TDD断言无法做到的事情:告诉你调用堆栈中的哪个位置以及哪个客户 - 供应商关系被打破,以及为什么(这也立即暗示如何)要解决这个问题).为什么这是真的?
TDD断言旨在告诉您事后代码运行(执行)的结果.TDD断言只能看到被检查方法的当前状态.TDD断言不能从他们在代码库外部的位置做出来的,就是要准确地告诉你哪个调用失败了,为什么!你看 - 你对某种方法的初始TDD调用将触发该方法.很多时候,这个方法会调用另一个,另一个,另一个 - 有时候,当调用堆栈上下移动时,有一个断裂:有些东西写错了,根本没写,或者写它什么时候不应该.
TDD就像是在谋杀案发生后警察出现在犯罪现场.他们所留下的只是法医线索,他们希望这些线索会引导他们成为嫌疑人和信徒.但是,如果犯罪发生的话,我们可以在那里怎么办?这是TDD断言的放置与合同断言之间的区别.合同是为了捕捉正在进行的犯罪,他们直接指向罪犯,因为它正在犯罪!
让我们回顾一下.
TDD与DbC并不矛盾.
它是一种补充和一组合作的技术,但具有不同的功能和用途,以及与它们一起工作的工具.
合同进一步扩展,并在您的代码中断时显示更多信息.
TDD是执行合同的催化剂的一种形式.
在一天结束时:我想要两个!阅读完所有内容后(如果幸存下来),我希望你也能这样做.
按合同设计和测试驱动的开发并不是相互排斥的.
Bertrand Meyer的书" 面向对象的软件构建",第2版并没有说你永远不会犯错误.实际上,如果你看一下"当合同被破坏"这一章时,它会讨论当一个函数未能完成其契约所述的情况时会发生什么.
使用DbC技术这一简单事实并不能使您的代码正确无误.按合同设计以合同的形式为您的代码及其用户建立明确定义的规则.这很有帮助,但无论如何你总是可以把事搞得一团糟,只有你之前可能会注意到的.
测试驱动开发将从外部世界检查黑盒子样式,即您的类的公共接口行为正确.
我认为最好同时使用这两种方法而不是一种方法.
在我看来,在课堂上完全执行合同及其方法本身似乎是不切实际的.
例如,如果函数说它将通过某种方法对字符串进行散列并将散列字符串作为输出返回,那么该函数如何强制正确地对字符串进行散列?再次哈希,看看它们是否匹配?看起来很傻.反转哈希以查看您是否获得原始哈希?不可能.相反,您需要一组测试用例来确保函数正常运行.
另一方面,如果您的特定实现要求您的输入数据具有一定的大小,那么建立合同并在您的代码中强制执行它似乎是最好的方法.
在我看来,TDD更具"归纳感".您从示例(测试用例)开始,您的代码体现了这些示例的一般解决方案.
在收集了确定对象行为和合同的要求之后,DBC似乎更具"演绎性".然后,您可以编写这些合同的具体实现.
编写合同有点困难,比作为具体行为示例的测试更为困难,这可能是TDD比DBC更受欢迎的部分原因.
我过去曾使用过这两种版本,发现DBC风格较少"侵入性".DBC的驱动程序可能是常规应用程序运行.对于单元测试,您必须注意设置,因为您期望(验证)一些响应.对于DBC,您没有必要.规则以与数据无关的方式编写,因此无需设置和模拟.
更多关于我使用DBC/Python的经验:http://blog.aplikacja.info/2012/04/classic-testing-vs-design-by-contract/
我将按合同设计视为在所有情况下成功/失败的规范,而“测试驱动开发”则针对一种特定情况。如果TDD情况成功,则假定函数正在执行其工作,但未考虑可能导致其失败的其他情况。
另一方面,“按合同设计”并不一定要保证所需的答案,只是答案是“正确的”。例如,如果一个函数返回应该返回一个非空字符串,那么您可以在ENSURE中唯一假设的是它不会为null。
但是,也许它没有返回预期的字符串。合同无法确定这一点,只有测试可以证明它正在按照规范执行。
因此,两者是相辅相成的。
格雷格