2

假设我正在开发一种方法,我可以传入两个数字并取回最大的数字。由于我正在做这种 TDD 风格,我将进行十几个单元测试并提出以下实现:

public int GetHighestNumber(int x, int y)
{
    if (x > y) return x;
    else return y;
}

这非常有效。

测试已用于创建实现。实现完成后,保持这些单元测试有什么意义?这就像在房子已经交付后在房子周围保留脚手架一样。

我不是在寻找进行单元测试的理由,或者是否/何时使用 TDD。我只是好奇为什么在代码完全实现后应该保留单元测试,而不是将这些测试作为临时手段并在不需要支持开发人员时将其删除。


编辑

在对CodeGnome发表评论后,我意识到我应该强调这只是关于 TDD 测试,它的创建主要是为了将开发人员指向正确的方向。可以说是脚手架。

不过,我确实很欣赏所有的评论。


在阅读了所有的消息后,我可以总结出两件事:

  1. stackoverflow 上没有人真正阅读原始问题
  2. 你应该保留这些单元测试,不是因为测试本身有任何用处,而是因为你可能正在与非常不称职的同事一起工作,而这些测试是为了防止他们做难以置信的愚蠢事情
4

8 回答 8

9

Real life code is almost never "finished".

Requirements change and expand. Features are added. Despite TDD, there are bugs to be fixed.

Unit tests also work as regression tests, to prove that the code still works after these changes. This is in fact the most valuable aspect of unit tests (any TDD proponent who claims that it's less important that the design benefits is deluding themselves).

于 2012-08-03T09:06:21.963 回答
5

我想到了几个原因(至少):

  • 他们帮助记录您的代码应该/不应该做什么
  • 推论:它们为未来的开发人员提供了您方法的示例用法
  • 如果您想在以后更改方法,它们充当回归测试:您可以检查更改后原始测试是否仍然通过,并且您没有破坏任何东西

编辑

“不会对该方法进行任何更改,并且不需要在该级别进行回归测试”

直到某个聪明人到来并将代码更改为

if (x - y > 0) return x;
else return y;

(是的,它可以而且将会发生,如果不是用那种方法,在其他地方)。

希望您的测试之一是:

assertEquals(Integer.MAX_VALUE, GetHighestNumber(Integer.MIN_VALUE, Integer.MAX_VALUE));

并且该测试将失败。

于 2012-08-03T09:06:47.223 回答
3

测试防止回归

正确实施的单元测试可验证程序行为。“遗留”测试确保当您扩展或修改程序时,您不会意外引入新的错误或在代码中创建回归。

如果随着代码的发展,测试不再具有明确的目的,那么测试当然可以与代码一起重构。但是,如果一个特性一开始就值得测试,那么在代码的整个生命周期中继续测试该行为仍然很有用。

如果你发现你的单元测试包含很多“无用”的测试,你可能正在测试错误的东西(例如组合而不是行为),或者可能只是需要重构你的测试以防止测试随着时间的推移变得陈旧。我当然不会建议完全删除它们,因为它们是完整性检查,而不仅仅是遗留的脚手架。

于 2012-08-03T09:11:30.430 回答
1

回归,回归,百万次回归。

想象一下在实现功能后没有测试的假设情况:

  1. 您为您的应用程序实现了每月报告功能。例如,假设它从数据库中提取实体,扫描它们的定义并创建适当的表。
  2. 您发布了报告功能,您的客户很高兴这一切都可以正常工作并按照他的意愿创建漂亮的闪亮表格。
  3. 一段时间后,您的朋友在数据访问层上做了一些内部工作。他更改了一些实体定义以优化数据库内容。
  4. 你把它作为重要的补丁发布。客户再次高兴,您的应用程序现在似乎运行得更快了。
  5. 几天后,您接到恼怒的客户打来的电话,声称报告产生了一些垃圾。

谁该受责备?当然是你。发生了什么?您的代码停止工作。也许它根本没有用?也许它在一种特殊情况下有效?抛开这些愚蠢的问题,每个人都知道到底发生了什么;实体代码中的更改破坏了报告生成功能。你的朋友造成了问题,你承担责任。

这就是你有自动化测试的原因(不仅仅是单元自然)。防止倒退。手动发现由回归引起的错误非常困难。它们可能会在更改后立即出现,稍后会出现,稍后会出现其他更改。你永远不会知道。这就是单元测试(以及其他测试方法)提供帮助的地方。从上面的假设情况来看,如果他运行测试,您的朋友会知道他破坏了您的报告生成器代码。就那么简单。

这个programs.SE问题的评论很少:

大学将单元测试暴露为您出于纯粹的信念必须做的事情,而没有详细解释有一种疾病需要预防和控制,并且这种疾病称为回归

单元测试并不能证明您的代码没有缺陷,但它们确实提高了您的信心(或应该......)代码按照您设计的方式执行,并且明天继续执行今天的任务。

每当您开始怀疑自己是否真的需要所有这些测试时,请记住一些事情。

于 2012-08-03T09:49:07.523 回答
0

使用上面的代码,您可以进行以下测试:

  • X 大于 Y,X 返回。
  • Y大于X,返回Y。
  • X 等于 Y,返回 Y。

因此,三个简短、快速运行的测试记录了该方法的预期行为并强制执行。(保留它们的两个很好的理由)。

现在,如果您的测试数量明显多于上述三个测试,那么您可以开始减少测试数量。像所有代码一样,它应该在不需要时进行重构和删除,但请确保您只删除不需要的部分。

于 2012-08-03T10:01:15.977 回答
0

测试应该作为模块的文档为您的团队服务。它们向读者展示了进入值和预期退出值的示例。他们应该明确指示下一个人如何调用例程。

我在上面的很多评论中注意到,您对下一个程序员会出现很多假设,他会很聪明且能干,他会仔细阅读您的代码,他会信任您的方法名称。做出这些假设是危险的。也许今天不在你的老板手下,但很可能在某个时候你的代码将由比你当前假设所表明的能力差的人维护。

可以这样想:如果你的代码是开源的,突然数百名不同水平的人都在看它,他们都会理解吗?可能不是。

对模块的作者来说显而易见的东西对其他人来说通常并不那么明显。

于 2012-08-05T00:28:22.480 回答
0

TDD 越相关,项目必须扩展的规模就越大。

在处理大型复杂项目时,各个级别的代码部分之间存在许多依赖关系。对某一点的错误修复可以通过自动化测试立即发现其他部分的新错误。

一旦你在这样一个大项目中获得经验,你就会看到它的价值。

于 2012-08-03T09:49:36.017 回答
0

TDD 摇滚。您正确的 TDD 有助于设计。因此,没有失败的测试在编写代码后几乎没有价值,或者没有价值。然而,它们的存在确实提供了一种安全带的感觉,以鼓励重构。但是,如果您的代码遵循开放/封闭原则,那么一旦编写代码,测试可能会被认为不再有用。

我遵循的规则是,如果原始设计成立并且我正在对需求进行微小的更改(例如构建带有小写前缀的字符串),那么测试确实有帮助,它们避免了回归。但是,如果我意识到该类的设计或意图是完全错误的,那么单元测试可能会阻碍更改(更改不等于重构)。

困难的部分是从需求的变化中识别意图的变化。

像往常一样,没有非黑即白的答案。

于 2012-08-03T09:15:25.943 回答