【发布时间】:2010-08-20 20:02:08
【问题描述】:
查看this 等帖子,似乎进行 TDD 的正确方法是为某个功能编写测试,让该功能通过,然后添加另一个测试并根据需要进行重构,直到它通过,然后重复。
我的问题是:为什么要使用这种方法?我完全理解写测试第一的想法,因为它有助于你的设计。但是我为什么不为特定函数创建所有测试,然后一次全部实现该函数,直到所有测试都通过?
【问题讨论】:
标签: unit-testing tdd
查看this 等帖子,似乎进行 TDD 的正确方法是为某个功能编写测试,让该功能通过,然后添加另一个测试并根据需要进行重构,直到它通过,然后重复。
我的问题是:为什么要使用这种方法?我完全理解写测试第一的想法,因为它有助于你的设计。但是我为什么不为特定函数创建所有测试,然后一次全部实现该函数,直到所有测试都通过?
【问题讨论】:
标签: unit-testing tdd
该方法来自极限编程原则“你不需要它”。如果您实际上编写了一个测试,然后编写了使其通过的代码,然后重复该过程,您通常会发现您编写的代码足以让事情正常进行。您不会发明不需要的新功能。你不处理不存在的极端情况。
尝试一个实验。写出你认为需要的测试清单。把它放在一边。然后采用一次一个测试的方法。看看列表是否不同以及为什么。当我这样做时,我几乎总是以更少的测试结束。我几乎总是发现我发明了一个如果我先做所有测试就不需要的案例。
【讨论】:
对我来说,这是关于“思想负担”。如果我同时担心所有可能的行为,我的大脑就会紧张。如果我一次接近他们,我可以全神贯注地解决眼前的问题。
【讨论】:
我相信这源于“YAGNI”(“你不需要它”)(*) 的原则,它指出类应该尽可能简单,没有额外的功能。因此,当您需要一个功能时,您为它编写一个测试,然后您编写该功能,然后停止。如果您首先编写了许多测试,显然您只是在猜测您的 API 在未来某个时间点需要是什么。
(*) 我一般把它翻译成“你太笨了,不知道未来需要什么”,但那是另一个话题……
【讨论】:
恕我直言,它减少了过度设计您正在编写的代码的机会。
当您查看不同的使用场景时,添加不必要的代码会更容易。
【讨论】:
Dan North 建议不存在测试驱动设计之类的东西,因为设计并没有真正被测试驱动——这些单元测试只有在功能实现后才成为测试,但在设计阶段你真的以身作则设计。
这是有道理的——您的测试设置了一系列样本数据和条件,被测系统将在这些数据和条件下运行,并且您根据这些示例场景进行设计。
其他一些答案表明这是基于 YAGNI。这部分是正确的。
不过,除此之外,还有复杂性问题。正如人们常说的,编程就是管理复杂性——将事物分解成可理解的单元。
如果你编写 10 个测试来覆盖 param1 为 null、param2 为 null、string1 为空、int1 为负数以及一周中的当前日期是周末的情况,然后去实现它,你必须一次处理很多复杂性。这为引入错误开辟了空间,并且很难理清测试失败的原因。
另一方面,如果您编写第一个测试来覆盖一个空字符串1,您几乎不必考虑实现。测试通过后,您将转到当前日期是周末的情况。您查看现有代码,很明显逻辑应该去哪里。你运行测试,如果第一个测试现在失败了,你知道你在实现星期几的事情时打破了它。我什至建议你在测试之间提交源代码,这样如果你破坏了某些东西,你总是可以恢复到通过状态并重试。
一次只做一点,然后验证它是否有效,大大减少了引入缺陷的空间,当您的测试在实施后失败时,您只更改了很少的代码,很容易识别缺陷并纠正它,因为你知道现有的代码已经正常工作了。
【讨论】:
这是一个很好的问题。您需要在可能的测试范围内编写所有测试与最可能的用户场景之间找到平衡。恕我直言,一项测试还不够,我通常喜欢编写 3 或 4 个测试来代表该功能最常见的用途。我也喜欢写一个最好的案例测试和一个最坏的案例测试。
编写许多测试有助于您预测和了解您的功能的潜在用途。
【讨论】:
我相信 TDD 提倡一次编写一个测试,因为它迫使您按照在每个步骤中执行可能工作的最简单的事情的原则进行思考发展。
【讨论】:
我认为您发送的文章正是答案。如果您首先编写所有测试和所有场景,您可能会编写代码来同时处理所有这些场景,并且大多数时候您最终可能会得到处理所有这些相当复杂的代码。
另一方面,如果你一次只做一个,你最终会每次都重构你现有的代码,最终得到的代码可能对所有场景都尽可能简单。
就像您在问题中提供的链接一样,如果他们先编写所有测试,我敢肯定他们不会以简单的 if/else 语句结束,但可能是相当复杂的递归部分代码。
【讨论】:
原理背后的原因很简单。坚持到底有多实用是一个单独的问题。
原因是,如果您编写的代码超过了通过当前测试所需的代码,那么您就是在编写未经测试的代码。 (这与 YAGNI 无关。)
如果您编写下一个测试以“赶上”生产代码,那么您只是编写了一个您没有看到失败的测试。该测试可能被称为“TestNextFeature”,但它也可以return true 来获取您所拥有的所有证据。
TDD 就是要确保所有代码(生产和测试)都经过测试,并且所有那些讨厌的“但我确信我写对了”的错误不会进入代码中。
【讨论】:
我会按照你的建议去做。为特定功能编写多个测试,实现该功能,并确保该功能的所有测试都通过。这可确保您了解函数的用途和用法,而与您的实现分开。
【讨论】:
如果您需要比单元测试测试更多的实现方式,那么您的单元测试可能不够全面。
我认为这个想法的一部分是保持简单,保持设计/计划的功能,并确保您的测试足够。
【讨论】:
上面有很多很好的答案 - YAGNI 是第一个想到的答案。
关于“让测试通过”指南的另一重要之处在于,TDD 实际上是一个三个阶段的过程:
红色>绿色>重构
经常重新审视最后一部分,即重构,是 TDD 的许多价值体现在更简洁的代码、更好的 API 设计和对软件的更多信心方面。您需要在非常小的短块中进行重构,但以免任务变得太大。
很难养成这个习惯,但要坚持下去,因为一旦你进入循环,这是一种非常令人满意的工作方式。
【讨论】: