您的孩子在TDD中的表现如何?


37

今天,我们正在培训TDD,发现了以下误解。

该任务是针对输入的“ 1,2”返回数字总和为3的。我用C#编写的是:

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]); //task said we have two numbers and input is correct

但是其他人更喜欢用其他方式。首先,对于输入“ 1,2”,他们添加了以下代码:

if (input == "1,2")
   return 3;

然后,他们为输入“ 4,5”引入了另一个测试,并更改了实现:

if (input == "1,2")
   return 3;
else if (input == "4,5")
   return 9;

之后,他们说“好的,现在我们可以看到模式了”,并执行了我最初所做的工作。

我认为第二种方法更适合TDD定义,但是...我们应该对此严格吗?对我来说,如果我确定我不会跳过任何步骤,则可以跳过琐碎的婴儿步骤并将其合并为“ twinsteps”。我错了吗?

更新。我没有弄清楚这不是第一个测试,所以犯了一个错误。已经有一些测试,所以“返回3”实际上并不是满足要求的最简单的代码。


25
太小了,我的同事们涌出“ Ooahhh dazso cuuuuute”
Adel

6
@Adel:我的早餐几乎被cho住了,键盘现在已经满了,或者有口感和面包屑
Binary Worrier

2
@Adel对于非母语人士,我很难理解这种幽默,但我想您的同事喜欢这个问题:)
SiberianGuy

8
@Idsa:当显示一个孩子的第一步时,这是同事的回应。当看到Adel编写的单元测试时,看着“单元测试”婴儿步骤,他们说“哦,太可爱了”。对-实际-婴儿步骤的反应=对单元测试“婴儿步骤”的反应。
Binary Worrier

3
@Binaryworrier希望我能为您提供一些真正的观点,以便抽出时间来解释亲戚
Andrew T

Answers:


31

编写使测试通过的最简单的代码。

据我所知,你们俩都没有这样做。

宝贝第1步。

测试:对于输入“ 1,2”,返回数字之和为3

使测试失败:

throw NotImplementedException();

使测试通过:

return 3;

宝贝第2步。

测试:对于输入“ 1,2”,返回数字总和,即3

测试:对于输入“ 4,5”,返回数字总和,即9

第二次测试失败,因此使其通过:

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]);

(比if ... return列表更简单)

在这种情况下,您当然可以争论“显而易见的实现”,但是,如果您正在谈论严格按照婴儿步骤进行操作,那么这是正确的步骤,IMO。

参数是,如果您不编写第二个测试,则稍后可能会出现一些亮点,并“重构”您的代码以使其读取:

return input.Length; # Still satisfies the first test

而且,如果不采取这两个步骤,则您永远不会使第二项测试变红(这意味着该测试本身是可疑的)。


关于您的input.Length示例,以同样的成功,我可以想象一些疯狂的错误实现,两种测试都不会发现这种错误
SiberianGuy

@Idsa-是的,绝对可以,编写的测试越多,实现的过程就越疯狂。input.Length并不是那么遥不可及,特别是如果该方法的输入恰好是某处某个文件的度量值,而您不建议调用该方法Size()
pdr

6
+1。关于如何学习TDD,这是正确的方法。了解了它之后,有时可能会直接使用显而易见的实现,但是要了解一下TDD,这会更好。
Carl Manaster 2011年

1
我有一个关于“测试”本身的问题。您会为输入“ 4,5” 编写测试还是修改原始测试?
mxmissile 2011年

1
@mxmissile:我会写一个新的测试。它并不需要花费很多时间,并且在以后进行重构时,您得到的测试数量会增加一倍,从而保护了您。
pdr

50

我认为第二种方式是头脑麻木。我看到了执行足够小的步骤的价值,但是编写这些微小的合子(甚至不能称其为婴儿)步骤只是愚蠢而浪费时间。特别是如果您要解决的原始问题本身已经很小。

我知道这是培训,更多的是展示原理,但是我认为这样的例子对TDD的弊大于弊。如果要显示婴儿脚步的价值,请至少使用其中有价值的问题。


+1,感谢您让我查找并学习一个新词(asinine)
Marjan Venema

12
+1叫它麻木。TDD很好,但是像任何现代的炒作编程技术一样,您应注意不要迷失其中。
stijn 2011年

2
“特别是如果您要解决的原始问题本身已经很小的话。” -如果输入是要添加的两个整数,则我会同意这一点,但是我不相信何时“拆分字符串,从结果中解析两个整数并将它们相加”。现实世界中的大多数方法都没有比这复杂得多。实际上,应该进行更多的测试,以涵盖诸如查找两个逗号,非整数值之类的
极端情况。– pdr

4
@pdr:我同意你的看法,应该有更多的测试来处理极端情况。当您编写它们并注意到您的实现需要更改以处理它们时,请务必这样做。我想我对第一个幸福的路径“显而易见的实现”采取合子步骤只是一个问题,而不是仅仅写下来然后从那里开始。我看不出写一个if语句的价值,因为我的身体中的每根纤维都知道会在下一刻消失。
Christophe Vanfleteren 2011年

1
@ChristopheVanfleteren:当Beck描述明显的实现时,他以两个整数的总和为例,并且仍然会发出巨大的戏剧性警告:如果您的设计者/审稿人可以想到使代码变得更简单的代码,您将如何丢人。测试通过。如果您只为此场景编写一个测试,那是绝对的确定性。另外,我可以想到至少三种“明显”的方法来解决此问题:拆分并添加,用+替换逗号并求值,或使用正则表达式。TDD的重点是驱动您做出正确的选择。
pdr

19

肯特·贝克(Kent Beck)在他的《测试驱动开发:通过示例》一书中对此进行了介绍。

您的示例指示“ 显而易见的实现 ”-您想返回两个输入值之和,这是一个相当基本的算法。您的反例属于“伪造它直到制造出来”(尽管是非常简单的情况)。

显而易见的实现可能比这复杂得多-但基本上在方法的规范非常严格时它就会启动-例如,返回类属性的URL编码版本-您不需要花很多时间在伪造的编码。

另一方面,数据库连接例程将需要更多的思考和测试,因此没有明显的实现(即使您可能已经在其他项目上写过几次)。

从书中:

当我在实践中使用TDD时,通常会在这两种实现方式之间切换;当一切运行顺利并且我知道要键入什么内容时,我会在“明显实现”之后放入“明显实现”(每次都要运行测试以确保对我来说很明显)对计算机来说仍然很明显)。一旦出现意外的红条,我便立即备份,转向伪造的实现,并重构为正确的代码。当我恢复信心时,我将返回“显而易见的实现”。


18

我认为这是遵循法律的条文,而不是法律的精神。

您的宝贝步骤应为:

尽可能简单,但再简单不过。

而且,方法中的动词是 sum

if (input == "1,2")
   return 3;

不是总和,而是对特定输入的测试。


4

对我来说,将几个琐碎的实现步骤组合成一个琐碎的琐事似乎很好-我也一直这样做。我认为没有必要对每次遵循TDD都发牢骚。

OTOH仅适用于上述示例中非常简单的步骤。对于任何更复杂的事情,我无法立即完全记住和/或对结果没有110%的把握,我宁愿一次走一步。


1

正如这个问题所说明的那样,当初次踏上TDD之路时,步伐的大小可能是一个令人困惑的问题。刚开始编写测试驱动的应用程序时,我经常问自己一个问题。我正在编写的测试是否有助于推动应用程序开发?这看似微不足道,与某些人无关,但和我一起呆了片刻。

现在,当我着手编写任何应用程序时,通常会从测试开始。测试的多少步骤很大程度上与我对我要执行的操作的理解有关。如果我认为我的课堂行为举足轻重,那么这将是一个很大的进步。如果我要解决的问题还不清楚,那么该步骤可能就是我知道将要使用一个名为X的方法,并且该方法将返回Y。这时该方法甚至没有任何参数,并且方法的名称和返回类型可能会更改。在这两种情况下,测试都在推动我的发展。他们告诉我有关我的申请的事情:

我脑子里的这节课真的上课了吗?

要么

我到底要怎么做?

关键是我可以在眨眼之间在大步和小步之间切换。例如,如果大步不起作用,而我看不到明显的解决方法,我将切换到小步。如果那不起作用,我将切换到更小的步骤。然后还有其他技术,例如三角剖分(如果我真的被卡住的话)。

如果像我一样,您是开发人员而不是测试人员,那么使用TDD的目的不是编写测试,而是编写代码。如果没有给您带来任何好处,请不要为编写小型测试而烦恼。

我希望您喜欢TDD训练棒。恕我直言,如果更多的人被测试感染,那么世界将是一个更好的地方:)


1

在有关单元测试的入门文章中,我读到了相同的方法(看起来非常非常小的步骤),并回答了我喜欢的东西“它们应该有多小”这个问题(措辞如下):

这是关于您对步骤有效的信心。如果需要,您可以迈出真正的大步。但是,只要尝试一段时间,您就会发现对您认为理所当然的地方有很多误导的信心。因此,测试可帮助您建立基于事实的置信度。

因此,也许您的同事有点害羞:)


1

只要测试成功,不是整个方法都无关紧要吗?在第二个示例中,扩展测试将更快失败,但是在两种情况下都可能导致失败。


1
如果您完全不关心浪费时间,则无关紧要
SiberianGuy11

1

我同意人们所说的那样,这不是最简单的实现。

该方法如此严格的原因是,它迫使您编写尽可能多的相关测试。为一个测试用例返回一个恒定值并称其为pass是可以的,因为它会迫使您返回并指定您真正想要的东西,以便从程序中获得无用的东西。使用这种微不足道的案例在某些方面会使您无所适从,但原理是,当您尝试“过多”执行并将要求降至最简单的实现时,错误会渗入规范的空白中,从而确保必须针对您实际想要的行为的每个独特方面编写测试。


我添加了有关“返回恒定值”的更新
SiberianGuy
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.