为什么在进行TDD时不一次编写所有测试?


54

TDD的红色-绿色-重构周期已经建立并被接受。我们编写一个失败的单元测试,并使其尽可能简单地通过。与为一个类编写许多失败的单元测试并使它们全部一次性通过相比,这种方法有什么好处。

测试套件仍可保护您避免在重构阶段编写错误的代码或犯错误,那么这有什么危害呢?有时,首先以“大脑转储”的形式首先编写一个类(或模块)的所有测试会更容易,以便一次编写所有预期的行为。


20
做一些最适合您的事情(经过一些实验)。盲目遵循教条从来不是一件好事。
Michael Borgwardt'4

6
我敢说一次编写所有测试就像一次编写所有应用程序代码一样。
Michael Haren

1
@MichaelHaren所有针对类(或功能模块)的测试,对不起引起混淆
RichK 2012年

3
解决“大脑转储”问题:有时候,当您意识到需要进行几种不同的特定输入测试时,测试/编码中就有一些要点,并且有一种趋势是想在不专心的情况下利用实现的清晰度。编码的细节。我通常通过维护一个单独的列表(例如Mylyn)或在Test类中包含我想记住要测试的不同事物的注释列表(例如//测试null大小写)来进行管理。但是,我仍然一次只编写一个测试代码,而是系统地从列表中查找。
山姆·戈德堡

1
好吧,我不知道为什么没人提这个,但是你不能一次写所有的测试。事先编写所有测试与进行BDUF完全相同。历史对我们有什么启示?它几乎永远都行不通。
Songo 2013年

Answers:


49

测试驱动的设计是关于正确使用API而不是代码。

首先编写最简单的失败测试的好处是,使您的API(实际上是您在动态设计中)尽可能地简单。在前面。

任何将来的用途(您编写的下一个测试所用的内容)都将来自最初的简单设计,而不是次要的设计来应对更复杂的情况。


优分!有时我们沉迷于测试代码,以至于有时甚至在编写第一个测试之前就忽略了API和域模型的重要性。
maple_shaft

+1用于实际解决测试驱动开发的意图。
Joshua Drake'4

76

当您编写一个测试时,您会专注于件事。
通过许多测试,您可以将注意力转移到许多任务上,所以这不是一个好主意。


8
谁会对此表示反对?
CaffGeek

6
@Chad我不是拒绝投票的人,但是我相信这个答案很明显。测试驱动开发是关于使用测试来驱动代码的设计。您可以单独编写测试以发展设计,而不仅仅是为了提高可测试性。如果只是关于测试工件,那么这将是一个很好的答案,但是因为它缺少一些关键信息。
2012年

7
我没有拒绝投票,但是;我想过这个问题。对一个复杂问题的答案太简短了。
Mark Weston

2
+1一次专注于一件事情,我们的多任务能力被高估了。
cctan 2012年

这是可能可行的最简单答案。
DNA

26

编写单元测试时的困难之一是您正在编写代码,而代码本身可能容易出错。在编写实现代码时,由于重构工作,最终还可能需要稍后更改测试。使用TDD,这意味着您可能最终会在测试中有些失落,并发现自己需要在项目过程中随着实施的成熟而重写大量“未经测试”的测试代码。避免此类问题的一种方法是简单地专注于一次做一件事情。这样可以确保将所有更改对测试的影响最小化。

当然,这很大程度上取决于您编写测试代码的方式。您是针对每种方法编写单元测试,还是针对功能/需求/行为编写测试?另一种方法可能是将“行为驱动”方法与合适的框架一起使用,并专注于编写测试,就像它们是规范一样。这意味着要么采用BDD方法,要么采用BDD测试(如果您希望更正式地坚持使用TDD)。另外,您可能会完全遵循TDD范式,但是会更改编写测试的方式,这样,您就不必再完全专注于测试方法,而可以更一般地测试行为,从而满足所实现需求特征的特定要求。

无论您采用哪种具体方法,在上述所有情况下,您都使用测试优先方法,因此,虽然可能很容易将大脑下载到一个漂亮的测试套件中,但您也想与诱惑要做的比绝对必要的多。每当我要开始一个新的测试套件时,我都会对自己重复YAGNI,有时甚至将其放入代码中的注释中,以提醒我继续专注于紧迫的事情,并只做满足要求的最低要求。我即将实现的功能要求。坚持红绿重构有助于确保您将执行此操作。


很高兴您指出了如何编写测试代码之间的区别。有些人喜欢编写单个主单元测试,该测试涵盖单个函数或方法的输入的所有现实可能性。其他人在单元测试中采用了更多的BDD方法。当确定编写整套测试是否很重要是否必然是不好的做法时,这种区别很重要。伟大的见识!
maple_shaft

17

我认为通过这样做,您会错过TDD 的过程。通过仅在开始时编写所有测试,您实际上并没有完成使用TDD进行开发的过程。您只是在猜测您将需要哪些测试。如果您在开发代码时一次进行一次测试,则这将与最终编写的测试完全不同。(除非您的程序本质上是微不足道的。)


1
大多数业务和企业应用程序在技术上本质上都是微不足道的,并且鉴于大多数应用程序在业务和企业中的表现如何,因此大多数应用程序在本质上也是微不足道的。
maple_shaft

5
@maple_shaft-技术可能很琐碎,但业务规则却并非如此。尝试为5位具有不同要求的经理创建一个应用程序,他们拒绝听一些有关您的简约,优雅,少即是多,简约设计的BS的信息。
JeffO

5
@JeffO 1)不是BS。2)优雅的简约设计需要良好的软件开发技能。3)能够减轻5位不同的经理的需求的能力,他们每周的工作时间不会超过5分钟,并且仍然坚持极简的设计,因此需要出色的软件开发人员。 专家提示:软件开发不只是编码技能,还包括谈判,对话和取得所有权。你一定是阿尔法狗,有时会咬回去。
maple_shaft

1
如果我理解正确,那么这个答案就是一个问题。
Konrad Rudolph

1
@maple_shaft我认为这就是Jeff O的评论所见,不是吗?
ZweiBlumen'4

10

我确实在“脑力激荡”时“编写”了所有我能想到的测试,但是我将每个测试写成描述该测试的一个注释

然后,我将一个测试转换为代码并进行工作,以使其编译并通过。通常,我认为我并不需要我认为曾经做过的所有测试,或者我需要其他测试,这些信息仅来自编写代码以使测试通过。

问题在于,除非创建了要测试的方法和类,否则您无法在代码中编写测试,否则您将得到很多编译器错误,这些错误会导致您一次进行单个测试。

现在,如果使用“英语”编写测试时使用的是规格流程之类的系统,则您可能希望让客户在有空的时候同意一系列测试,而不是仅创建一个测试。


1
是的,虽然同意上面的答案,指出了首先对所有测试进行编码的问题,但我发现将我对当前方法应如何作为一组测试描述的行为(没有任何代码)表现出的整体理解非常有帮助。写下这些的过程倾向于澄清我是否完全了解我将要编写的代码的要求,以及是否有我没有考虑过的极端情况。在概述了该方法应如何工作的“概述”之后,我发现自己更适合编写第一个测试,然后使其通过。
Mark Weston

10

TDD背后的想法是快速迭代。

如果必须编写大量测试,然后再编写代码,则很难迭代重构代码。

没有简单的代码重构,您将失去TDD的许多好处。


5

在与TDD我的(有限)的经验,我可以告诉你,每次我打破了以时间来写一个测试的学科时,事情已经过去了严重。这是一个容易陷入的陷阱。“哦,这种方法很简单,”您自己想,“所以我将淘汰另外两个相关的测试并继续前进。” 好吧,你猜怎么着?没有什么比看起来微不足道了。每次我陷入这个陷阱时,我都会调试一些我认为很容易的东西,但结果却遇到了奇怪的问题。而且由于我要一次编写多个测试,因此要跟踪错误的位置需要进行大量工作。

如果您需要大量信息,则有很多选择:

  • 白板
  • 用户故事
  • 评论
  • 好笔和纸

请注意,该列表上没有任何地方是编译器。:-)


5

您假设您在编写代码之前知道代码的外观。TDD / BDD和QA一样,既是设计/发现过程。对于给定的功能,您可以编写最简单的测试来验证该功能是否令人满意(由于功能的复杂性,有时可能需要多次测试)。您编写的第一个测试加载了对工作代码的外观的假设。如果在编写第一行代码以支持该测试套件之前就编写了整个测试套件,则可能会产生一系列未经验证的假设。相反,编写一个假设并进行验证。然后写下。在验证下一个假设的过程中,您可能只是打破了先前的假设,因此您必须返回并更改该第一个假设以匹配现实,或者更改现实以使第一个假设仍然适用。

考虑一下您在科学笔记本中编写的作为理论的每个单元测试。填写笔记本时,您可以证明自己的理论并形成新的理论。有时证明一个新理论会反驳一个先前的理论,因此您必须加以修正。一次证明一种理论比一次证明20种理论容易。


TDD假设您在编写代码之前也已经知道了代码的样子,只是小部分。
Michael Shaw

4

TDD是一种高度迭代的方法,根据我的经验,它更适合现实世界的开发方式。通常,我的实施过程是在此过程中逐步形成的,每个步骤都可能带来更多的问题,见解和测试思路。这是使我专注于实际任务的理想选择,并且非常高效,因为在任何时间点,我只需要在短期记忆中保留有限数量的内容。反过来,这减少了出错的可能性。

您的想法基本上是一种“前期大测试”方法,恕我直言,这种方法更难处理,并且可能变得更加浪费。如果您在工作中发现自己的方法不好,API有缺陷并且需要从头开始,或者改用第三方库,该怎么办?这样一来,预先编写测试的大量工作就浪费了精力。

就是说,如果这对您有用,那就好。我可以想象,如果您按照固定,详细的技术规范进行工作,在您经验丰富的领域和/或完成的工作量很小,那么您可能已经准备了大多数或所有必要的测试用例,并且可以清楚地实现开始。然后可能需要立即编写所有测试来开始。如果您的经验是,从长远来看,这会使您的工作效率更高,那么您不必太担心规则手册:-)


4

除了想一件事,TDD的一种范例是编写尽可能少的代码以通过测试。一次编写一个测试时,看到编写足以使该测试通过的代码的路径要容易得多。要通过一整套测试,您无需花很多时间就可以编写代码,而必须做出巨大的飞跃才能一次性通过所有测试。

现在,如果您不局限于编写代码以使它们全部“一次通过”,而是只编写足够多的代码来一次通过一个测试,则它可能仍然有效。但是,您必须有更多的纪律,不仅可以继续编写和编写超出所需数量的代码。一旦走上了这条路,您就可以编写超出测试描述范围的代码,而这些代码是未经测试的,至少在某种意义上说它不是由测试驱动的,或者在不需要的情况下(或行使)任何测试。

完全理解该方法应该做什么,例如注释,故事,功能说明等。我将等待一次将它们转换为测试。

一次编写所有测试可能会错过的另一件事是思考过程,通过该过程可以促使您想到其他测试用例。如果没有大量现有测试,则需要在上一个通过测试的上下文中考虑下一个测试用例。就像我说的那样,对方法应该做什么有一个很好的了解,但是很多时候我发现自己发现了一些新的可能性,而这些新的可能性我不是先验的,而只是在编写过程中才会发生。测试。除非您特别习惯于思考我可以编写哪些我还没有编写的新测试,否则您可能会错过这些测试。


3

我从事的项目中,编写(失败)测试的开发人员与实施必要代码以使其通过的开发人员不同,我发现它确实有效。

在那种情况下,仅与当前迭代相关的测试只编写一次。因此,您的建议在这种情况下是完全可能的。


2
  • 然后,您尝试一次专注于太多事情。
  • 在实施使所有测试通过的过程中,您没有应用程序的工作版本。如果必须执行很多,那么很长一段时间都将没有可用的版本。

2

Red-Green-Refactor循环是一个清单,供刚接触TDD的开发人员使用。我想说,遵循此清单是一个好主意,直​​到您知道何时遵循以及何时可以打破它为止(也就是说,直到不必在stackoverflow上问这个问题为止:)

完成TDD已有近十年的时间,我可以告诉您,在编写生产代码之前,很少(如果有的话)编写很多失败的测试。


1

您正在描述BDD,其中某些外部利益相关者具有可执行的规范。如果存在预先确定的预先规范(例如格式规范,工业标准或程序员不是领域专家的情况),则这可能是有益的。

然后,通常的方法是逐渐涵盖越来越多的验收测试,这是项目经理和客户可见的进度。

通常,您需要在BDD框架(例如Cucumber,Fitnesse或此类)中指定并执行此测试。

但是,这不是您要与单元测试混为一谈的单元测试,它与许多与API相关的极端情况,初始化问题等重点都集中在被测项目(这是一个实现工件)上,非常接近具体的实现细节。

红绿重构学科有很多好处,并且您可以希望通过在前面输入这些唯一的好处就是收支平衡。


1

一次测试:主要优势是专注于一件事。想一想深度优先的设计:您可以深入了解并通过快速反馈环保持专注。您可能会错过整个问题的范围!这就是(大型)重构起作用的时刻。没有它,TDD将无法正常工作。

所有测试:分析和设计可能会向您显示更多问题的范围。想想宽度优先的设计。您可以从多个角度分析问题,并从经验中补充意见。它本来就更难,但是如果“足够多”,可能会产生有趣的好处-减少重构。请注意,过度分析很容易,但完全没有达到目标!

我发现通常很难推荐偏爱其中一种,因为因素很多:经验(尤其是同一个问题),领域知识和技能,重构代码的友好性,问题的复杂性...

我猜想,如果我们将注意力集中在典型的业务应用程序上,那么TDD及其快速,几乎试错的方法通常会在有效性方面取胜。


1

假设您的测试框架支持它,那么我建议您不要实施您想转储的测试,而是编写描述性的待决测试,稍后再实现。例如,如果您的API应该执行foo和bar而不能执行biz,则只需为测试套件添加以下代码(此示例在rspec中),然后逐一攻击即可。您很快就会下定决心,可以一一解决所有问题。当所有测试通过时,您将知道何时解决了您在头脑风暴期间遇到的所有问题。

describe "Your API" do

  it "should foo" do
    pending "braindump from 4/2"
  end

  it "should bar" do
    pending "braindump from 4/2"
  end

  it "should not biz" do
    pending "braindump from 4/2"
  end

end
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.