25

我知道测试驱动开发的定义原则之一是您首先编写单元测试,然后编写代码以通过这些单元测试,但是有必要这样做吗?

我发现在编写之前我常常不知道我在测试什么,主要是因为我过去从事的几个项目更多地是从概念证明而不是设计出来的。

我以前尝试过编写单元测试,它可能很有用,但对我来说似乎并不自然。

4

20 回答 20

39

这里有一些很好的评论,但我认为有一件事被忽略了。

编写测试首先驱动您的设计。这是重要的一步。如果您“同时”或“不久之后”编写测试,您可能会错过以微步骤执行 TDD 的一些设计优势。

起初感觉真的很俗气,但看到事情在你眼前展开成你最初没有想到的设计,真是太棒了。我已经看到它发生了。

TDD 很难,并不适合所有人。但是如果你已经接受了单元测试,那就试试一个月,看看它对你的设计和生产力有什么影响。

您花在调试器上的时间更少,而花更多时间思考由外而内的设计。这是我书中的两个巨大优势。

于 2008-10-29T15:08:11.270 回答
9

研究表明,在编写代码之后编写的单元测试是更好的测试。但需要注意的是,人们不倾向于在事件发生后写它们。因此,TDD 是一个很好的折衷方案,因为至少要编写测试。

因此,如果您在编写代码后编写测试,对您有好处,我建议您坚持下去。

我倾向于发现我做混合。我对需求了解得越多,我可以预先编写的测试就越多。当需求——或者我对问题的理解——薄弱时,我倾向于在之后编写测试。

于 2008-10-29T14:56:35.927 回答
7

TDD 不是关于测试,而是关于测试如何驱动你的代码。因此,基本上您正在编写测试以让架构自然发展(并且不要忘记重构!!!否则您不会从中获得太多好处)。之后您拥有大量回归测试和可执行文档是一个很好的副作用,但不是 TDD 背后的主要原因。

所以我的投票是:先测试

PS:不,这并不意味着您不必事先规划您的架构,而是如果测试告诉您这样做,您可能会重新考虑它!!!!

于 2008-10-29T15:32:02.563 回答
6

在过去的 6-7 年里,我一直领导着开发团队。我可以肯定的是,作为一名开发人员和与我共事过的开发人员,如果我们知道我们的代码适合大局的位置,它会在代码质量方面产生显着差异。

测试驱动开发 (TDD) 帮助我们回答“什么?” 在我们回答“如何?”之前 它有很大的不同。

我理解为什么在 PoC 类型的开发/架构师工作中可能会担心不遵循它。你是对的,遵循这个过程可能没有完全的意义。同时,我想强调的是,TDD 是一个属于开发阶段的过程(我知道这听起来已经过时了,但你明白了:)当低级规范明确时。

于 2008-10-29T15:48:45.993 回答
3

我认为首先编写测试有助于定义代码实际应该做什么。很多时候人们对代码应该做什么或应该如何工作没有一个很好的定义。他们只是开始写作并随着他们的进展而弥补。首先创建测试使您专注于代码将执行的操作。

于 2008-10-29T15:10:51.407 回答
2

并非总是如此,但我发现当我这样做时它确实有帮助。

于 2008-10-29T14:50:47.093 回答
2

我倾向于在编写代码时编写它们。最多我会在编写之前编写测试类/模块是否存在。

我没有计划足够多的细节来比要测试的代码更早地编写测试。

我不知道这是否是我的思维或方法的缺陷,或者只是 TIMTOWTDI。

于 2008-10-29T14:55:33.887 回答
2

我从我想如何调用我的“单元”开始并使其编译。像:

picker = Pick.new
item=picker.pick('a')
assert item

然后我创建

class Pick
 def pick(something)
 return nil
 end
end

然后我继续在我的“测试”用例中使用 Pick,这样我就可以看到我希望如何调用它以及如何处理不同类型的行为。每当我意识到我可能在某些边界或某种错误/异常上遇到问题时,我都会尝试让它触发并获得一个新的测试用例。

所以,简而言之。是的。之前做测试的比例比不做的要高很多。

于 2008-10-29T14:57:16.113 回答
2

指令是关于如何做事来提高整体质量或生产力甚至最终产品的建议。它们绝不是要遵守的法律,除非你被正确的编码实践之神一闪而过。

这是我对拍摄的妥协,我发现它非常有用且富有成效。

通常最难解决的部分是需求,紧随其后的是你的类、API、包的可用性......然后是实际的实现。

  1. 编写你的界面(它们会改变,但在知道必须做什么方面会有很长的路要走)
  2. 编写一个简单的程序来使用接口(它们愚蠢的主要)。这在确定如何使用它方面有很长的路要走(根据需要经常回到 1)
  3. 在接口上编写测试(我从 TDD 集成的位,根据需要再次回到 1)
  4. 在接口后面编写实际代码
  5. 在类和实际实现上编写测试,使用覆盖工具确保你不会忘记奇怪的执行路径

所以,是的,我在编码之前编写测试,但在我弄清楚需要用一定程度的细节做什么之前从来没有。这些通常是高级测试,仅将整体视为黑匣子。通常将保留为集成测试,一旦接口稳定,不会有太大变化。

然后我在其背后的实现上编写了一堆测试(单元测试),这些测试将更加详细,并且随着实现的发展,随着它的优化和扩展而经常改变。

这是严格意义上的 TDD 吗?极端?敏捷...?任何... ?我不知道,坦率地说,我不在乎。它对我有用。我会根据需要和我对软件开发实践的理解进行调整。

我的 2 美分

于 2008-10-29T15:38:36.840 回答
2

我已经编程了 20 年,而且我几乎从来没有写过没有运行某种单元测试的代码行——老实说,我知道人们一直都在这样做,但是有人怎么能发布一个没有经过某种测试运行的代码行超出了我的范围。

通常,如果没有合适的测试框架,我只会在我编写的每个类中编写一个 main()。它给你的应用程序增加了一点麻烦,但我猜有人可以随时删除它(或注释掉它)。我真的希望你的类中只有一个 test() 方法可以自动编译出发布版本——我喜欢我的测试方法和我的代码在同一个文件中......

所以我已经完成了测试驱动开发和测试开发。我可以告诉你,当你是一个初级程序员时,TDD 真的很有帮助。它可以帮助您学习“从外部”查看代码,这是程序员可以学习的最重要的课程之一。

TDD 还可以帮助您在遇到困难时继续前进。你可以只写一些你知道你的代码必须做的非常小的部分,然后运行它并修复它——它会让人上瘾。

另一方面,当您添加到现有代码并且几乎确切地知道您想要什么时,这是一个折腾。您的“其他代码”经常测试您的新代码。您仍然需要确保测试每条路径,但只需从前端运行测试即可获得良好的覆盖率(动态语言除外——对于那些你真的应该对所有内容进行单元测试的语言)。

顺便说一句,当我在一个相当大的 Ruby/Rails 项目中时,我们的测试覆盖率非常高。我们将一个主要的中心模型类重构为两个类。这需要我们两天时间,但通过所有测试,我们不得不重构它,最终接近两周。测试不是完全免费的。

于 2008-10-29T16:19:45.993 回答
2

我不确定,但是从您的描述中,我感觉到对 test-first 的实际含义可能存在误解。这并不意味着您首先编写所有测试。这确实意味着你有一个非常紧凑的周期

  1. 编写一个最小的测试
  2. 通过编写必要的最少生产代码使测试通过
  3. 编写下一个将失败的测试
  4. 通过以最简单的方式更改现有的生产代码,使所有现有的测试通过
  5. 重构代码(测试和生产!),使其不包含重复并具有表现力
  6. 继续 3. 直到你想不出另一个合理的测试

一个周期 (3-5) 通常只需要几分钟。使用这种技术,您实际上可以在并行编写测试和生产代码的同时改进设计。根本没有涉及太多的前期设计。

关于它是否“必要”的问题——不,显然不是。不做 TDD 成功的项目数不胜数。但是有一些强有力的证据表明,使用 TDD 通常会显着提高质量,而且通常不会对生产力产生负面影响。而且也很有趣!

哦,关于它感觉不“自然”,这只是你习惯的问题。我知道有些人非常沉迷于每隔几分钟获得一个绿色条(“所有测试通过”的典型 xUnit 标志)。

于 2008-11-01T20:59:42.350 回答
2

现在有这么多的答案,他们都是不同的。这与外面的现实完全相似。每个人都在做不同的事情。我认为对单元测试存在巨大的误解。在我看来,好像人们听说过 TDD 并且他们说它很好。然后他们开始编写单元测试,但并没有真正理解 TDD 到底是什么。他们刚刚得到“哦,是的,我们必须编写测试”的部分并且他们同意它。他们也听说过“你应该先写你的测试”,但他们并不认真。

我认为这是因为他们不了解测试优先的好处,反过来,只有在您以这种方式完成一段时间后您才能理解。他们似乎总是找到 1.000.000 个借口来解释为什么他们不喜欢先编写测试。因为在弄清楚所有东西如何组合在一起时太难了等等等等。在我看来,这都是他们逃避无法自律的借口,尝试测试优先的方法并开始看到好处。

如果他们开始争论“我不相信这个测试优先的事情,但我从来没有这样做过”,那是最可笑的事情......太好了......

我想知道单元测试最初来自哪里。因为如果这个概念真的源自 TDD,那么人们如何理解它是可笑的。

于 2010-12-01T21:06:52.657 回答
1

编写测试首先定义了您的代码的外观 - 即它倾向于使您的代码更加模块化和可测试,因此您不会创建具有非常复杂和重叠功能的“膨胀”方法。这也有助于将所有核心功能隔离在单独的方法中,以便于测试。

于 2008-10-29T15:22:45.033 回答
1

就我个人而言,我相信如果在编写代码之前没有完成单元测试,它们的效率就会大大降低。

测试的一个古老问题是,无论我们多么努力地思考它,我们永远不会想出每一个可能的场景来编写一个测试来覆盖。

显然,单元测试本身并不能完全阻止这种情况,因为它是限制性测试,只查看一个代码单元,不涵盖此代码与其他所有内容之间的交互,但它首先为编写干净的代码提供了良好的基础,应该至少限制模块之间交互问题的机会。我一直致力于保持代码尽可能简单的原则——事实上,我相信这是 TDD 的关键原则之一。

所以从一个测试开始,基本上说你可以创建一个这种类型的类并构建它,理论上,为每一行代码编写一个测试,或者至少覆盖一段特定代码的每条路线。随手设计!显然是基于最初制作的粗略设计,为您提供一个可以工作的框架。

正如您所说,一开始是非常不自然的,而且似乎是在浪费时间,但我亲眼目睹了从长远来看,当缺陷统计数据出现并显示使用 TDD 完全编写的模块时,它会得到回报随着时间的推移,缺陷比其他产品低得多。

于 2008-10-29T15:54:48.727 回答
1

之前,期间和之后。之前是规范的一部分,合同,工作的定义期间是在实施过程中发现特殊情况,不良数据,异常的情况。之后是维护、进化、改变、新的需求。

于 2008-10-29T22:23:45.213 回答
0

我不会先编写实际的单元测试,但我会在开始编码之前制作一个测试矩阵,列出所有可能需要测试的场景。我还列出了在对程序的任何部分进行更改时必须测试的案例列表,作为回归测试的一部分,除了全面测试代码位之外,该回归测试将涵盖应用程序中的大多数基本场景改变了。

于 2008-10-29T14:56:21.373 回答
0

请记住,通过 Extreme 编程,您的测试实际上就是您的文档。所以如果你不知道你在测试什么,那么你就不知道你想要你的应用程序做什么?

您可以从“故事”开始,可能类似于

“用户可以获取问题列表”

然后,当您开始编写代码来解决单元测试时。要解决上述问题,您至少需要一个用户和问题类。那么你就可以开始考虑这些领域了:

“用户类具有名称 DOB 地址 TelNo 锁定字段”

等希望它有所帮助。

狡猾

于 2008-10-29T15:00:27.667 回答
0

是的,如果您使用的是真正的 TDD 原则。否则,只要您编写单元测试,您就比大多数人做得更好。

根据我的经验,在编写代码之前编写测试通常更容易,因为这样做可以为自己提供一个简单的调试工具,以便在编写代码时使用。

于 2008-10-29T15:00:48.307 回答
0

我同时写它们。我为新类和测试类创建了框架代码,然后为某些功能编写了一个测试(这有助于我了解我希望如何调用新对象),并在代码中实现它。

通常,我第一次不会得到优雅的代码,它通常很hacky。但是一旦所有的测试都工作了,你就可以重构,直到你最终得到一些非常干净、整洁并且可以证明是坚如磐石的东西。

于 2008-10-29T15:01:06.637 回答
0

当你在写一些你习惯写的东西时,它会有所帮助,首先写下你会定期检查的所有东西,然后再写那些功能。更多时候,这些功能对于您正在编写的软件来说不是最重要的。现在,在另一边没有灵丹妙药,事情不应该一成不变。开发人员的判断在决定使用测试驱动开发还是测试后开发的决策中起着重要作用。

于 2008-10-29T22:12:52.240 回答