与测试驱动开发相比,为什么按合同设计不那么流行?
您可能认为这个问题就像之前在 StackOverflow 上提出的这个问题。 但我正在尝试以不同的方式看待事物。
在 TDD 中,我们编写包含不同条件、标准、验证码的测试。 如果一个班级通过了所有这些测试,我们就可以开始了。 这是确保班级确实完成其应该做的事情而不是其他事情的一种方法。
如果你逐字逐句地遵循Bertrand Meyers的书面向对象的软件构建,类本身具有内部和外部契约,因此它只做它应该做的事情,而不做任何事情别的。 不需要外部测试,因为确保遵循契约的代码是类的一部分。
简单的例子让事情变得清楚
TDD
创建测试以确保在所有情况下值的范围都在 (0-100)
创建一个包含通过测试的方法的类。
DBC
- 创建一个类,为该成员
var
创建一个范围为 (0-100) 的契约,设置违反契约的契约,定义一个方法。
我个人喜欢 DBC 方法。
纯DBC不那么受欢迎有什么原因吗? 是语言、工具还是敏捷,还是只是我喜欢让代码对自己负责?
如果您认为我的想法不对,我非常愿意学习。
You may think this question is like this question asked on StackOverflow earlier. But I am trying to look at things differently.
In TDD, we write tests that include different conditions, criteria, verification code. If a class passes all these tests we are good to go. It is a way of making sure that the class actually does what it's supposed to do and nothing else.
If you follow Bertrand Meyers' book Object Oriented Software Construction word by word, the class itself has internal and external contracts, so that it only does what its supposed to do and nothing else. No external tests required because the to code to ensure contract is followed is the part of the class.
Quick example to make things clear
TDD
Create test to ensure that in all cases a value ranges from (0-100)
Create a class containing a method that passes the test.
DBC
- Create a class, create a contract for that member
var
to range from (0-100), set contract for contract breach, define a method.
I personally like the DBC approach.
Is there a reason why pure DBC is not so popular? Is it the languages or tools or being Agile or is it just me who likes to have code responsible for itself?
If you think I am not thinking right, I would be more than willing to learn.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
DBC 的主要问题是在绝大多数情况下,要么无法正式指定合约(至少不方便),要么无法用现有的静态分析工具进行检查。 在主流语言(不是 Eiffel)突破这一点之前,DBC 不会提供人们需要的那种保证。
在 TDD 中,测试是由人类根据方法的当前自然文本规范编写的,(希望)有详细记录。 因此,人类通过编写测试来解释正确性,并根据该解释获得一些保证。
如果您阅读 Sun 的 JavaDoc 编写指南,它会说文档本质上应该列出足以编写测试计划的合同。 因此,契约设计不一定与 TDD 相互排斥。
The main problem with DBC is that in the vast majority of cases, either the contract cannot be formally specified (at least not conveniently), or it cannot be checked with current static analysis tool. Until we get past this point for mainstream languages (not Eiffel), DBC will not give the kind of assurance that people need.
In TDD, tests are written by a human being based on the current natural-text specifications of the method that are (hopefully) well-documented. Thus, a human interprets correctness by writing the test and gets some assurance based on that interpretation.
If you read Sun's guide for writing JavaDocs, it says that the documentation should essentially lay out a contract sufficient to write a test plan. Hence, design by contract is not necessarily mutually exclusive with TDD.
TDD 和 DbC 是两种不同的策略。 DbC 允许在运行时快速失败,而 TDD 则“在编译时”起作用(确切地说,它在编译后立即添加一个新步骤来运行单元测试)。
这是 TDD 相对于 DbC 的一大优势:它可以更早地获得反馈。 当您以 TDD 方式编写代码时,您会同时获得代码及其单元测试,您可以根据您在测试中编码的想法验证它“有效”。 使用 DbC,您可以获得带有嵌入式测试的代码,但您仍然需要练习它。 IMO,这当然是 Dbc 不那么受欢迎的原因之一。
其他优点:TDD 创建了一个自动测试套件,允许检测(读取预防)回归并进行重构< /a> 安全,以便您可以逐步扩展您的设计。 DbC 不提供这种可能性。
现在,使用 DbC 进行快速失败非常有帮助,特别是当您的代码与其他组件交互或必须依赖外部源时,在这种情况下测试合约可以节省您的时间。
TDD and DbC are two different strategies. DbC permits fail-fast at runtime while TDD act "at compile time" (to be exact it add a new step right after the compilation to run the unit tests).
That's a big advantage of TDD over DbC : it allows to get earlier feedback. When you write code the TDD way, you get the code and its unit-tests at the same time, you can verify it "works" according to what you thought it should, which you encoded in the test. With DbC, you get code with embedded tests, but you still have to exercise it. IMO,this certainly is one reason that Dbc is not so popular.
Other advantages : TDD creates an automatic test suite that allow detecting (read preventing) regressions and make Refactoring safe, so that you can grow your design incrementally. DbC does not offer this possibility.
Now, using DbC to fail-fast can be very helpful, especially when your code interfaced other components or has to rely on external sources, in which case testing the contract can save you hours.
首先,我是一名 Eiffel 软件工程师,所以我可以从经验来谈这个问题。
TDD vs DbC的前提是不正确的
这两种技术并不相互矛盾,而是相互补充。 补语与断言和目的的放置有关。
TDD 的目的既有组成部分,也有范围。 TDD 的基本组成部分是布尔断言和对象特征(例如方法)执行。 步骤很简单:
失败的断言,测试失败。 通过所有断言是目标。
与 TDD 一样,按合同设计的合同也有目的、范围和组件。 虽然 TDD 仅限于单元测试时间,但合约可以贯穿整个 SDLC(软件开发生命周期)! 在 TDD 范围内,对象方法(功能)的执行将执行合约。 在 Eiffel Studio 自动测试 (TDD) 设置中,创建一个对象,进行调用(就像其他语言中的 TDD),但相似之处到此为止。
在带有自动测试和带有合同的 Eiffel 代码的 Eiffel Studio 中,目的有所改变。 我们想测试客户与供应商的关系。 我们的 TDD 代码假装是其对象上的供应商方法的客户端。 我们基于此目的创建对象并调用方法,而不仅仅是简单化的“TDD 式方法测试”。 因为对我们的方法(功能)的调用具有契约,所以这些契约将作为自动测试中 TDD 式代码的一部分执行。 因为这是事实,所以我们放置在代码中的契约断言(测试)不必出现在 TDD 测试代码中。 我们(作为程序员)的工作就是简单地确保:A)代码+合约沿着所有N条路径执行,B)代码+合约使用所有合理的数据类型和范围执行。
关于 TDD-DbC 互补关系也许还有更多可写的内容,但我不会在这个问题上对你粗鲁。 可以这么说,TDD 和 DbC 并不互相矛盾——绝对不是!
DbC 合约的威力超出了 TDD 所能达到的范围
现在,我们可以将注意力转向 Design-by-Contract 合约的威力,超出了 TDD 所能达到的范围!
合约存在于代码中。 它们不是外部的,而是内部的。 关于合同的最强大的一点(超出了客户-供应商合同关系的基础)是编译器被设计为了解它们! 它们不是埃菲尔铁塔的附加补充! 因此,它们参与继承的各个方面(传统的垂直继承和横向或水平泛型)。 而且,它们到达了 TDD 无法到达的地方——方法(特性)内部。
虽然 TDD 可以轻松地模拟前置条件和后置条件,但 TDD 无法到达代码内部并执行循环不变合约,也无法在执行时沿着代码块定期抽查“检查”合约。 这是一个强大的逻辑和定性范式,也是合同设计如何运作的现实。
此外,TDD 只能以最微弱的方式实现类不变量。 我已尽最大努力让我的自动测试代码(这实际上只是应用 TDD 的 Eiffel Studios 版本)来进行类不变模仿。 这不可能。 要理解为什么,您必须了解 Eiffel 类不变量如何工作的详细信息。 因此,目前,您只需相信(或不相信)我的话,TDD 无法完成这项任务,而 DbC 处理得如此轻松、良好且优雅!
DbC 的影响范围并没有以上述概念结束。
我们在上面注意到 TDD 存在于单元测试时间。 契约,因为它们在编译器的监督和控制下应用在代码中,所以适用于可以执行代码的任何地方:
工作台:作为程序员,您正在使用代码来查看它的工作情况(例如在 TDD 之前) 。
单元测试:您的持续集成测试、单元测试、TDD 等。
单元
Alpha 测试:您的初始测试测试用户在运行可执行文件时会被合约绊倒
Beta 测试:更广泛的用户也会被合约绊倒。
生产:最终的可执行文件(或生产系统)可以通过合同进行持续测试(TDD 不能)。
在上述每种情况下,人们都会发现可以控制哪些合约运行以及来自哪些来源! 您可以选择性地、细粒度地打开和关闭各种形式的合约,并以极高的精度控制编译器应用它们的位置和时间!
如果所有这些还不够,合约(根据设计)可以做一些 TDD 断言无法做到的事情:告诉您调用堆栈中的哪个位置以及哪个客户-供应商关系被破坏,以及为什么 > (这也立即建议如何修复它)。 为什么这是真的?
TDD 断言旨在告诉您事后代码运行(执行)的结果。 TDD 断言只能看到所检查方法的当前状态。 TDD 断言无法从代码库外部的位置准确地告诉您哪个调用失败以及原因! 您会看到,您对某个方法的初始 TDD 调用将触发该方法。 很多时候,该方法会调用另一个、另一个、另一个——有时,随着调用堆栈上下左右,就会出现损坏:某些数据写入错误,根本不写入,或者写入当它不应该的时候。
TDD 就像谋杀案发生后警察出现在犯罪现场。 他们只剩下法医线索,希望能找到嫌疑人并定罪。 但如果我们能在犯罪发生时在场呢? 这就是 TDD 断言和合约断言的放置之间的区别。 合同的目的是捕捉正在进行的犯罪行为,并且在犯罪者实施犯罪行为时直接指向犯罪者!
回顾
让我们回顾一下。
TDD 与 DbC 并不矛盾。
它是一组技术的补充和协作,但具有不同的功能和目的,以及与之配合使用的工具。
合同更进一步,并在代码中断时揭示有关代码的更多信息。
TDD 是合约执行的催化剂之一。
归根结底:我两个都想要! 阅读完所有这些(如果您幸存下来),我希望您也能这样做。
First of all, I am an Eiffel software engineer, so I can speak to the matter from experience.
The premise of TDD vs DbC is incorrect
The two technologies are not at odds with each other, but complementary to each other. The complement has to do with the placement of assertions and purpose.
The purpose of TDD has both components and scope. The basic components of TDD are boolean assertions and object feature (e.g. method) execution. The steps are simple:
Assertions that fail, fail the test. Passing all assertions is the goal.
Like TDD, the contracts of Design-by-Contract have purpose, scope, and components. While TDD is limited to unit-test-time, contracts can live through the entire SDLC (Software Development Life-cycle)! Within the scope of TDD, execution of object methods (features), will execute the contracts. In an Eiffel Studio Auto-test (TDD) setup, one creates an object, makes the call (just like TDD in other languages), but here is where likeness ends.
In Eiffel Studio with Auto-test and Eiffel code with contracts, the purpose changes somewhat. We want to test the Client-Supplier relationship. Our TDD code is pretending to be a Client of our Supplier method on its object. We create our objects and call the methods based on this purpose, and not just simplistic "TDD-ish method testing". Because the calls to our methods (features) have contracts, those contracts will execute as a part of our TDD-ish code in Auto-test. Because this is true, contract assertions (tests) that we place in our code do NOT have to appear in our TDD test code. Our job (as a programmer) is to simply ensure: A) The code + contracts are executed along all N-paths, and B) The code + contracts are executed using all reasonable data types and ranges.
There is perhaps more to write about the TDD-DbC complement relationship, but I won't be boorish with you on the matter. Suffice it to say that TDD and DbC are NOT at odds with other—not by a long shot!
The power of the contracts of DbC beyond where TDD can reach
Now, we can turn our attention to the power of the contracts of Design-by-Contract beyond where TDD can reach!
Contracts live in the code. They are not external to it, but internal. The most powerful bit (beyond their client-supplier contract relationship basis) about contracts is that the compiler is designed to know about them! They are NOT a bolt-on addition to Eiffel! Thus, they participate in every aspect of inheritance (both traditional vertical is-a inheritance and in lateral or horizontal Generics). Moreover, they reach to a place that TDD cannot reach—inside the method (feature).
While TDD can mimic pre-conditions and post-conditions with some ease, TDD cannot reach inside the code and perform loop-invariant contracts, nor can it do periodic spot-check "check" contracts along a block of code as it is executing. This is a powerful logical and qualitative paradigm, and a reality about how design-by-contract works.
Moreover, TDD cannot do class invariants but in the faintest of ways. I have tried my hardest to get my Auto-test code (which is really just Eiffel Studios version of applied-TDD) to do class invariant mimicry. It is not possible. To understand why you would have to know the in's-and-out's of how Eiffel class invariants work. So, for the moment, you will simply have to either take my word for it (or not) that TDD is incapable of this task, that DbC handles so easily, well, and elegantly!
The reach of DbC does not end with the above notions
We noted above that TDD lives at unit-test-time. Contracts, because they are applied in code under the supervision and control of the compiler, apply anywhere that the code can be executed:
Workbench: you, as a programmer, are using the code to see it work (e.g. before TDD-time or in conjunction with TDD-time).
Unit-test: your continuous integration testing, unit-testing, TDD, etc.
Alpha-test: your initial test users will trip over contracts as they run the executable
Beta-test: a wider audience of users will also trip over contracts.
Production: the final executable (or production system) can have continual testing applied through contracts (TDD cannot).
In each of the situations above, one will find that one has control over just which contracts run and from what sources! You can selectively and fine-grainly turn on and off various forms of contracts and control with extreme precision where and when they are applied by the compiler!
And if all of this was not enough, contracts (by design) can do something that no TDD assertion can ever do: tell you where in the call-stack and which client-supplier relationship is broken, and why (which also immediately suggests how to fix it). Why is this true?
TDD assertions are designed to tell you about the results of a code-run (execution) after the fact. TDD assertion can only see as far as the current state of the method under examination. What TDD assertions cannot do from their position on the outside of the code-base is to tell you precisely which call failed and why! You see—your initial TDD call to some method will trigger that method. Many times, that method will call another, and another, and another—sometimes, as the call-stack winds up and down and hither and yon, there is a breakage: Something writes data wrong, does not write it at all, or writes it when it ought not.
TDD is like the police showing up to the crime scene after the murder has already happened. All they have left is forensic clues that they hope will lead them to a suspect and a conviction. But what if we could be there as the crime was taking place? That is the difference between the placement of TDD assertions and contract assertions. Contracts are there to catch the crime in progress and they point directly at the offender as it is committing the offense!
Recap
Let's recap.
TDD is not at odds with DbC.
It is a complement and a cooperative set of technologies, but with different functions and purposes, as well as tools to work with them.
Contract reach further and reveal more about your code when it breaks.
TDD is one form of catalyst for contracts to be executed.
At the end of the day: I want both! After reading all of this (if you survived), I hope you do as well.
合同设计和测试驱动开发并不相互排斥。
Bertrand Meyer 的书面向对象的软件构建,第二版并没有说你永远不会犯错误。 事实上,如果您查看“当契约被破坏时”一章,它讨论了当函数无法完成其契约规定的情况时会发生什么。
使用 DbC 技术这一简单事实并不能使您的代码正确。 按合同设计以合同的形式为您的代码及其用户建立了明确定义的规则。 这很有帮助,但无论如何你总是可能把事情搞砸,只是你可能会更早注意到。
测试驱动开发将从外部世界以黑盒方式检查类的公共接口是否行为正确。
Design-by-contract and test-driven development are not mutually exclusive.
Bertrand Meyer's book Object Oriented Software Construction, 2nd Edition doesn't say that you never make mistakes. Indeed, if you look at the chapter "When the contract is broken", it discusses what happens when a function fails to accomplish what its contract states.
The simple fact that you use the DbC technique doesn't make your code correct. Design-by-contract establishes well-defined rules for your code and its users, in the form of contracts. It's helpful, but you can always mess things up anyway, only that you'll probably notice earlier.
Test-driven development will check, from the outside world, black box style, that the public interface of your class behaves correctly.
我认为最好结合使用这两种方法,而不是只使用其中一种。
在我看来,在类及其方法本身中完全执行契约可能是不切实际的。
例如,如果一个函数表示它将通过某种方法对字符串进行哈希处理并将哈希后的字符串作为输出返回,那么该函数如何强制该字符串已正确哈希? 再次散列一下,看看它们是否匹配? 看起来很傻。 反转哈希值看看是否得到原始的? 不可能。 相反,您需要一组测试用例来确保函数正常运行。
另一方面,如果您的特定实现要求输入数据具有一定的大小,那么建立契约并在代码中强制执行它似乎是最好的方法。
I think it is best to use both methods in conjunction rather than just one or the other.
It has always seemed to me that fully enforcing a contract within the class and its methods themselves can be impractical.
For example, if a function says it will hash a string by some method and return the hashed string as output, how does the function enforce that the string was hashed correctly? Hash it again and see if they match? Seems silly. Reverse the hash to see if you get the original? Not possible. Rather, you need a set of test cases to ensure that the function behaves correctly.
On the other hand, if your particular implementation requires that your input data be of a certain size, then establishing a contract and enforcing it in your code seems like the best approach.
在我看来,TDD 更“归纳”。 您从示例(测试用例)开始,您的代码体现了这些示例的通用解决方案。
DBC 似乎更“演绎”,在收集需求后确定对象行为和契约。 然后,您对这些合约的具体实现进行编码。
编写契约有点困难,比具体行为示例的测试更困难,这可能是 TDD 比 DBC 更受欢迎的部分原因。
In my mind TDD is more "inductive". You start with examples (test cases) and your code embodies the general solution to those examples.
DBC seems more "deductive", after gathering requirements you determine object behavior and contracts. You then code the specific implementation of those contracts.
Writing contracts is somewhat difficult, more so than tests that are concrete examples of behavior, this may be part of the reason TDD is more popular than DBC.
我将契约设计视为所有情况下成功/失败的规范,而测试驱动开发则针对一个特定情况。 如果 TDD 案例成功,则假定某个函数正在执行其工作,但它没有考虑可能导致其失败的其他情况。
另一方面,按合同设计不一定保证所需的答案,只是答案是“正确的”。 例如,如果函数返回应该返回非空字符串,那么您在 ENSURE 中唯一可以假设的是它不会为空。
但也许它不会返回预期的字符串。 合同无法确定这一点,只有测试才能表明它是按照规范执行的。
所以两者是互补的。
格雷格
I see Design By Contract as a specification for success/failure in ALL cases, whereas Test Driven Development targets ONE specific case. If the TDD case succeeds, then it is assumed a function is doing it's job, but it doesn't take into account other cases that could cause it to fail.
Design By Contract on the other hand doesn't necessary guarantee the desired answer, only that the answer is "correct." For example, if a function returns is supposed to return a non-null string, the only thing you can assume in the ENSURE is that it will not be null.
But maybe it doesn't not return the string that was expected. There is no way for a contract to be able to determine that, only a Test can show that it was performing according to the specification.
So the two are complementary.
Greg
我认为两者没有理由不能共存。 看到一个方法,一眼就知道契约是什么,真是太棒了。 知道我可以运行我的单元测试并且知道我上次的更改没有任何问题,这也很棒。 这两种技术并不相互排斥。 为什么合同设计没有更受欢迎仍然是个谜。
I see no reason why both cannot co-exist. It is wonderful to look at a method and know at a glance exactly what the contract is. It is also wonderful to know that I can run my unit tests and know that nothing was broken with my last change. The two techniques are not mutually exclusive. Why design by contract is not more popular is a mystery.
我过去使用过这两种方式,发现DBC 风格的“侵入性”较小。 DBC 的驱动程序可能是正在运行的常规应用程序。 对于单元测试,您必须负责设置,因为您期望(验证)一些响应。 对于 DBC,您不必这样做。 规则以数据无关的方式编写,因此无需设置和模拟。
更多关于我使用 DBC/Python 的经验:http:// /blog.aplikacja.info/2012/04/classic-testing-vs-design-by-contract/
I've used both in the past and found DBC-style less "intrusive". The driver for DBC may be regular application running. For Unit Tests you have to take care of setup because you expect (validate) some responses. For DBC you don't have to. Rules are written in data-independent manner, so no need to setup and mocking around.
More on my experiences with DBC/Python: http://blog.aplikacja.info/2012/04/classic-testing-vs-design-by-contract/