通过单元测试有什么意义?

时间:2012-08-03 09:01:15

标签: unit-testing tdd

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

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

这非常有效。

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

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


修改

在评论 CodeGnome 之后,我意识到我应该强调这只是TDD测试,主要是为了让开发人员指向正确的方向。脚手架可以这么说。

我很欣赏所有评论。


阅读完所有信息后,我可以得出两件事:

  1. stackoverflow上没有人真正阅读原始问题
  2. 您应该保留这些单元测试,不是因为测试本身有任何用处,而是因为您可能正在与高度无能的同事合作,而这些测试可以防止他们做出令人难以置信的愚蠢事情

8 个答案:

答案 0 :(得分:9)

现实生活中的代码几乎从未“完成”。

需求变更和扩展。功能已添加。尽管有TDD,还有一些问题需要解决。

单元测试也可用作回归测试,以证明代码在这些更改后仍然有效。这实际上是单元测试中最有价值的方面(任何TDD支持者声称设计优势自欺欺人并不重要。)

答案 1 :(得分:5)

有几个原因(至少)浮现在脑海中:

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

修改

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

直到一些聪明人到达并将代码更改为

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

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

希望你的一个测试是:

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

该测试将失败。

答案 2 :(得分:3)

测试防止回归

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

如果代码随着代码的发展不再有明确的目的,那么测试肯定会与您的代码一起重构。但是,如果某个功能首先值得测试,那么在代码的整个生命周期中继续测试该行为仍然很有用。

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

答案 3 :(得分:1)

Regression, regression, for million times regression.

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

  1. 您为应用程序实现了montly-report功能。例如,假设它从数据库中提取实体,扫描它们的定义并创建适当的表。
  2. 您发布了报告功能,您的客户很高兴它能够正常工作并创建出色的闪亮表格,就像他想要的那样。
  3. 一段时间后,您的朋友在数据访问层上做了一些内部工作。他改变了几个实体定义来优化数据库事物。
  4. 您将此作为重要补丁发布。客户再次感到高兴,您的应用程序现在似乎工作得更快。
  5. 几天之后,您接到来自烦躁客户的电话,声称报告会产生一些垃圾。
  6. 谁应该受到责备?当然是你。发生了什么?你的代码停止了工作。也许它从来没有奏效?也许它适用于一个特例?抛开这个愚蠢的问题,每个人都知道究竟发生了什么;实体代码中的更改破坏了报告生成功能。你的朋友造成了这个问题,你承担了责任。

    这就是您进行自动化测试的原因(不仅仅是 unit )。要防止回归。很难发现手动回归引起的错误。它们可能会在更改后立即出现,一段时间后会出现,之后会有其他更改。你永远都不会知道。这就是单元测试(以及其他测试方法)有用的地方。从上面的假设情况来看,如果他已经运行测试,你的朋友就会知道他打破了你的报告生成器代码。就这么简单。

    来自this programmers.SE question的几点点评论:

      

    大学将单元测试视为纯粹信仰必须做的事情,而不详细解释有一种疾病可以预防和控制,并且该疾病被称为回归

         

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

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

答案 4 :(得分:0)

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

我遵循的规则是,如果原始设计成立并且我正在实现需求的微小更改(例如,构建具有小写前缀的字符串),测试确实有帮助,它们避免回归。但是,如果我意识到类的设计或打算是完全错误的,那么单元测试可能会成为改变的抑制因素(改变不等于重构)。

困难的部分是认识到需求变化的意图变化。

像往常一样没有黑白回答。

答案 5 :(得分:0)

TDD越具有相关性,项目必须扩大规模越大。

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

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

答案 6 :(得分:0)

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

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

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

现在,如果您有超过上述三项测试,那么您可以开始减少测试次数。与所有代码一样,它应该在不需要时进行重构和删除,但请确保删除不需要的代码。

答案 7 :(得分:0)

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

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

以这种方式思考:如果你的代码是开源的,突然有数百名具有不同能力水平的人正在研究它,他们都会理解它吗?可能不是。

对于其他人来说,模块作者显而易见的事情往往不那么明显。