28

我正在努力改进我们团队的开发过程,并且我正在考虑如何最好地通过测试驱动开发来实现按合同设计。似乎这两种技术有很多重叠,我想知道是否有人对以下(相关)问题有所了解:

  • 除非您使用某种代码生成器来生成基于合约的单元测试,否则拥有 TDD 和 DbC 是否违反 DRY 原则?否则,您必须在两个地方(测试和合同本身)维护合同,还是我遗漏了什么?
  • TDD 在多大程度上使 DbC 变得多余?如果我写的测试足够好,它们不等于写合同吗?如果我在运行时以及通过测试执行合同,我是否只会获得额外的好处?
  • 仅使用 TDD 而不是 TDD 和 DbC 是否更容易/更灵活?

这些问题的重点是这个更普遍的问题:如果我们已经在正确地进行 TDD,如果我们还使用 DbC,我们是否会从开销中获得显着的收益?

一些细节,虽然我认为这个问题很大程度上与语言无关:

  • 我们的团队非常小,<10 个程序员。
  • 我们主要使用 Perl。
4

8 回答 8

39

注意差异。

由合同驱动的设计。合同驱动设计

以测试驱动开发。测试驱动开发

它们是相关的,因为一个先于另一个。它们描述了不同抽象级别的软件。

当你去实施时,你会放弃设计吗?您是否认为设计文件违反了 DRY?您是否分别维护合同和代码?

软件是合同的一种实现方式。测试是另一回事。用户手册是第三本。操作指南是第四个。数据库备份/恢复过程是合同实施的一部分。

我看不到按合同设计的任何开销

  • 如果您已经在进行设计,那么您可以将格式从过多的单词更改为恰到好处的单词以概述合同关系。

  • 如果您不进行设计,那么编写合同将消除问题,降低成本和复杂性。

我看不出灵活性有任何损失。

  1. 从合同开始,

  2. 然后

    一种。编写测试和

    湾。写代码。

看看这两个开发活动是如何从本质上交织在一起的,并且都来自合同。

于 2008-12-27T03:13:15.637 回答
27

我认为 DbC 和 TDD 之间存在重叠,但是,我认为没有重复的工作:引入 DbC 可能会导致测试用例的减少。

让我解释。

在 TDD 中,测试并不是真正的测试。它们是行为规范。但是,它们也是设计工具:通过首先编写测试,您可以像用户一样使用被测对象的外部 API(您实际上还没有编写)。这样,您以一种对用户有意义的方式设计 API,而不是使您最容易实现的方式。类似的东西,queue.full?而不是queue.num_entries == queue.size.

第二部分不能被合同取代。

第一部分可以被合约部分替代,至少在单元测试中是这样。TDD 测试作为行为规范,对其他开发人员(单元测试)和领域专家(验收测试)都是如此。契约为其他开发人员、领域专家以及编译器和运行时库指定行为。

但是契约有固定的粒度:你有方法的前置条件和后置条件、对象不变量、模块契约等等。也许循环变体和不变量。然而,单元测试是测试行为单元。这些可能小于一个方法或由多个方法组成。这不是你可以用合同做的事情。对于“大图”,您仍然需要集成测试、功能测试和验收测试。

DbC 没有涵盖 TDD 的另一个重要部分:中间 D。在 TDD 中,测试驱动您的开发过程:除非您的测试失败,否则您永远不会编写一行实现代码,您永远不会编写一行代码测试代码 除非您的测试全部通过,否则您只需编写最少数量的实现代码即可使测试通过,您只需编写最少数量的测试代码即可产生失败的测试。

总之:使用测试来设计 API 的“流程”、“感觉”。使用合约来设计 API 的合约。使用测试为开发过程提供“节奏”。

像这样的东西:

  1. 为功能编写验收测试
  2. 为实现该功能的一部分的单元编写单元测试
  3. 使用您在步骤 2 中设计的方法签名,编写方法原型
  4. 添加后置条件
  5. 添加前置条件
  6. 实现方法体
  7. 如果验收测试通过,则转到 1,否则转到 2

如果你想知道按合同设计的发明者 Bertrand Meyer 对结合 TDD 和 DbC 的看法,他的小组有一篇很好的论文,叫做Contract-Driven Design = Test-Driven Development - Writing Test Cases。基本前提是合约提供了所有可能用例的抽象表示,而测试用例只测试特定用例。因此,可以从合约中自动生成合适的测试工具。

于 2008-12-27T13:35:13.710 回答
6

我要补充:

API 是程序员的契约,UI 定义是与客户端的契约,协议是客户端-服务器交互的契约。首先获得这些,然后您就可以利用并行开发轨道,而不会迷失在杂草中。是的,定期审查以确保满足要求,但不要在没有合同的情况下开始新的轨道。而“契约”是一个强有力的词:一旦部署,就决不能改变。您应该从一开始就包括版本管理和自省,对合同的更改仅由扩展集实现,版本号随之更改,然后您可以在处理混合或旧安装时执行优雅降级等操作。

我以艰难的方式吸取了这一教训,一个大型项目徘徊在从未有过的土地上,然后在严重的情况下以正确的方式应用它,公司生存,短导火线时间表。我们定义了协议,为交易的每一方定义并编写了一组协议仿真(基本上是罐装消息生成器和接收消息检查器,一个晚上的两脑编码),然后分开分别编写服务器端和客户端的应用程序。我们重新组合了演出之夜,而且效果很好。需求、设计、合同、测试、编码、集成。以该顺序。重复直到烘烤。

我对 TLA 的设计有点怀疑。与模式一样,符合流行语的配方是一个很好的指南,但根据我的经验,没有一刀切的设计或项目管理程序。如果你是按照书本 (tm) 做事,那么除非它是符合 DOD 程序要求的 DOD 合同,否则你可能会在此过程中的某个地方遇到麻烦。阅读书籍,是的,但一定要理解它们,然后还要考虑到团队中的人员方面。仅由 Book 强制执行的规则不会被统一执行 - 即使在工具强制执行时也可能会丢失(例如 svn 评论留空或神秘简短)。只有当工具链不仅强制执行程序,而且比任何可能的捷径更容易遵循程序时,程序才会被遵循。相信我,

于 2008-12-27T06:29:23.510 回答
2

您还可以使用以合同领域语言编写的可执行验收测试。它可能不是实际的“合同”,而是单元测试和合同之间的一半。

我会推荐使用 Ruby Cucumber http://github.com/aslakhellesoy/cucumber

不过既然你是Perl店,那也许你可以用我自己在p5-cucumber上的小尝试。 http://github.com/kesor/p5-cucumber

于 2009-03-13T11:42:49.883 回答
1

微软已经完成了基于代码契约和参数化单元测试的自动生成单元测试的工作。例如,合同规定当将一个项目添加到集合时计数必须增加一,参数化单元测试说明如何将“n”个项目添加到集合中。然后,Pex 将尝试创建一个单元测试来证明合同被破坏。有关概述,请参阅此视频

如果这可行,您的单元测试只需为您尝试测试的每件事编写一个示例,然后 PEX 将能够计算出会破坏测试的女巫数据项。

于 2009-03-13T12:21:35.110 回答
0

前段时间我对这个话题有过一些思考。

你可能想看看

http://gleichmann.wordpress.com/2007/12/09/test-driven-development-and-design-by-contract-friend-or-foe/

于 2009-12-07T07:59:57.250 回答
0

当你使用 TDD 来实现一个新方法时,你需要一些输入:你需要知道断言来检查你的测试。契约式设计为您提供了这些断言:它们是方法的后置条件和不变量。

于 2011-09-14T11:27:38.237 回答
0

我发现 DbC 对于启动红绿重构周期非常方便,因为它有助于识别单元测试开始。使用 DbC,我开始考虑被 TDD 编辑的对象必须处理的先决条件,并且每个先决条件都可能代表一个失败的单元测试,以启动一个红-绿-重构周期在某些时候,我切换到以失败的后置条件单元测试开始循环,然后保持 TDD 流程继续进行。我已经与 TDD 的新手尝试过这种方法,它确实有助于启动 TDD 思维方式。

总之,将 DbC 视为识别关键行为单元测试的有效方法。DbC 有助于分析输入(前置条件)和输出(后置条件),这是我们需要控制(输入)和观察(输出)以编写可测试软件(TDD 的类似目标)的两件事。

于 2014-10-29T23:40:54.163 回答