是否有过度的单元测试?

时间:2008-12-07 08:17:58

标签: unit-testing tdd

我对单元测试的概念并不陌生,但与此同时我还没有掌握它们。

我最近在使用TDD方法编写代码时编写单元测试时遇到的一个问题是:我应该在什么级别进行测试?

有时我想知道我是否过度使用单元测试。

开发人员应该在什么时候停止编写单元测试并完成实际工作?

在人们假设我反对使用TDD之前,我可能需要澄清这个问题......

我正在努力的是我的测试的粒度......

  • 当我的应用程序有配置文件时,我 测试可以检索的值 从文件?我倾向于......但...... ....
  • 然后我写一个 每个可能配置的单元测试 价值会出现吗?即检查它们是否存在......并且可以解析为正确的类型......
  • 当我的 应用程序将错误写入日志我需要 测试它是否能够写入 日志?我需要写吗? 测试以验证条目是否 实际做了日志吗?

我希望能够使用我的单元测试来验证我的应用程序的行为...但我不太确定在哪里停止。是否有可能编写过于微不足道的测试?

18 个答案:

答案 0 :(得分:31)

[更新:] 在TDD By Example - Pg194中找到了这个问题的简明答案。

  

简单的答案,由Phlip提供   是,“写作测试直到恐惧   变成无聊。“

[/的更新

我认为当前流行的问题是缺乏单元测试......而不是过度测试。我想我明白你会得到什么......我不会把它称为过度的单元测试,而是......在你专心致志的地方不聪明。

所以回答你的问题......一些准则。

  • 如果您遵循TDD ,您将永远不会拥有单元测试未涵盖的代码..因为您只编写(最小)代码以通过失败的单元测试而不再执行。推论:每个问题都应该通过单元测试失败,该测试可以确定缺陷的位置。相同的缺陷不应导致数十个UT同时中断
  • 不要测试你没有编写的代码。一个必然结果是:你没有测试框架代码(比如从app.config文件中读取值)你只是假设它有效。你有多少次破解框架代码?零点旁边。
  • 如果有疑问,会考虑失败的可能性,并将其与编写自动化测试用例的成本相比较。编写包含访问器/重复数据集测试的测试用例。
  • 解决痛苦。如果你发现你在某个区域定期遇到问题,那就把它放在测试工具下......而不是花时间为你知道非常可靠的区域编写冗余测试。 例如第三方/团队库不断破坏界面..不能像它应该的那样工作。模拟不会抓住它。如果您知道它是一个有问题的孩子,请使用真正的协作者并运行一些健全性测试来验证链接的回归类型套件。

答案 1 :(得分:9)

是的,确实有可能编写过多的单元测试。例如,

  • 测试getter和setter。
  • 测试基本语言功能是否有效。

答案 2 :(得分:8)

在实践中,问题不在于人们写了太多的测试,而是他们分布不均匀的测试。有时您会看到不熟悉单元测试的人会为易于测试的事情编写数百个测试,但是在他们最需要测试的地方之前他们会失去动力。

答案 3 :(得分:4)

绝对有可能过度单元测试,测试功能是一个很好的起点。但是不要忽视测试错误处理。当给定的输入不符合其前提条件时,您的单位应该做出明智的回应。如果您自己的代码负责输入错误,则断言失败是一种明智的反应。如果用户可能导致输入错误,那么您需要对异常或错误消息进行单元测试。

每个报告的错误都应至少导致一次单元测试。

关于你的一些细节:我肯定会测试我的配置文件解析器,看它能解析每个预期类型的​​每个值。 (我倾向于依赖Lua来配置文件和解析,但这仍然需要我做一些测试。)但我不会为配置文件中的每个条目编写单元测试;相反,我会编写一个表驱动的测试框架来描述每个可能的条目,并从生成测试。我可能会生成相同描述的文档。我甚至可以生成解析器。

当您的应用程序将条目写入日志时,您正在转向集成测试。更好的方法是拥有一个单独的日志记录组件,如syslog。然后,您可以对记录仪进行单元测试,将其放在架子上,然后重复使用。或者甚至更好,重用syslog。然后,简短的集成测试可以告诉您应用程序是否与syslog正确互操作。

一般情况下,如果你发现自己编写了很多单元测试,也许你的单位太大而且不够正交。

我希望其中一些有帮助。

答案 4 :(得分:3)

单元测试需要测试每个功能,边缘情况和有时候的角落情况。

如果您在测试边缘和角落情况后发现,您正在做“中间”情况,那么这可能是过度的。

此外,根据您的环境,单元测试可能要么非常耗时,要么非常脆弱。

测试确实需要持续维护,因此您编写的每个测试都可能在将来中断并需要修复(即使没有检测到实际的错误) - 尝试使用最少的测试数进行充分测试似乎是好的目标(但不要只是不必要地将几个测试拼凑成一个 - 一次测试一件事)

答案 5 :(得分:3)

根据给出的一些答案,有一点需要注意的是,如果您发现需要编写大量单元测试来反复执行相同的操作,请考虑重构相关代码的根本原因。

您是否需要为访问配置设置的所有位置编写测试?不可以。如果您重构并为功能创​​建单一入口点,则可以测试一次。我相信尽可能大量地测试功能。但重要的是要认识到,如果省略重构步骤,那么随着您在整个代码库中继续进行“一次性”实施,您的代码覆盖率将会大幅下降。

答案 6 :(得分:3)

我相信一个好的测试可以测试一些规范。任何测试不属于规范的东西的测试都是毫无价值的,因此应该省略,例如,测试方法只是实现单元的指定功能的方法。如果值得测试真正的微不足道的功能,例如getter和setter,这也是值得怀疑的,尽管你永远不知道它们会有多长时间是微不足道的。

根据规范进行测试的问题是许多人使用测试作为规范,这有很多原因是错误的 - 部分是因为它阻止你实际知道你应该测试什么和不测试什么(另一个重要原因是测试总是仅测试一些示例,而规范应始终指定所有可能的输入和状态的行为。

如果你的装置有适当的规格(你应该),那么显而易见的是需要测试的东西,除此之外的任何东西都是多余的,因而浪费。

答案 7 :(得分:2)

这是非常可能的,但问题是没有太多的测试 - 它正在测试你不关心的东西,或者投入太多的东西来测试更少的东西和更简单的测试就足够了。

我的指导原则是我在更改一段代码时的自信程度:如果它永远不会失败,我将不需要测试。如果它很简单,那么简单的理智就可以了。如果它很棘手,我会进行测试,直到我有信心做出改变。

答案 8 :(得分:2)

在单元测试中,您将编写一个测试,显示可以从配置文件中读取项目。您可以测试任何可能的怪癖,以便您拥有一组有代表性的测试,例如:你可以读取一个空字符串,一个长字符串,或一个带有转义字符的字符串,系统可以区分空字符串或缺少字符串。

完成该测试后,每次其他课程使用您已测试过的设施时,无需重新检查该功能。否则,对于您测试的每个功能,您都必须重新测试它所依赖的每个操作系统功能。对给定功能的测试只需要测试该功能的代码对正确的负责。

有时如果这很难判断,则表明需要重构才能使问题更容易回答。如果您必须为不同的功能多次编写相同的测试,这可能表明这些功能在其中共享可以移出到单个函数或类中的某些内容,测试一次然后重复使用。

从广义上讲,这是一个经济学问题。假设你已经停止了不必要的重复测试,你能承受多少测试才能完成?由于可能发生的情况组合,实际上不可能为任何非平凡的程序编写真正完整的测试,因此您必须进行调用。尽管最初推出时没有进行任何单元测试,但许多成功的产品已经占据了全球,包括一些有史以来最着名的桌面应用程序。它们不可靠,但足够好,如果他们在可靠性上投入更多,那么他们的竞争对手就会把它们打到市场份额的第一位。 (看看Netscape,他以一种众所周知的不可靠的产品获得第一名,然后当他们抽出时间以正确的方式做所有事情时完全消失)。这不是我们工程师想要听到的,希望这些日子客户更有眼光,但我怀疑不是很多。

答案 9 :(得分:2)

是的,单元测试可以超出/极端

请注意必要来测试功能 ;其他一切都来自那个

所以不,你不必测试你可以从配置文件中读取值,因为一个(或多个)功能需要从配置文件中读取值 - 如果没有,那么你不需要配置文件!

编辑:关于我想说的话似乎有些混乱。我不是说单元测试和功能测试是一回事 - 它们不是。每wikipedia:“一个单位是应用程序中最小的可测试部分”,逻辑上这样的“单位”比大多数“特征”要小。

我所说的是单元测试极端,并且很少是必要的 - 超级关键软件可能例外(例如生命可能受到威胁的实时控制系统,例如)或对预算和时间表没有限制的项目。

对于大多数软件,从实际角度来看,只需要测试功能。小于功能的测试装置不会受到伤害,这可能有所帮助,但是生产率与质量改进之间的权衡是值得商榷的。

答案 10 :(得分:1)

当您使用代码生成生成非常明显的单元测试时,通常会出现过多的单元测试。然而,由于生成的单元测试并没有真正伤害任何人(并且不会对成本效益比产生负面影响),我说让它们留下来 - 它们可能会在您最不期望的时候变得有用。

答案 11 :(得分:1)

当然,人们可以超越过度工程师的方式。

当您按照测试驱动开发进行操作时,您应该对代码有信心,并在您有足够的信心时停止。如有疑问,请添加新测试。

关于琐碎的测试,极限编程说这是“测试可能破坏的所有内容”。

答案 12 :(得分:1)

  

开发人员应该在什么时候停止编写单元测试并完成实际工作?

单元测试的重点 - 除了在设计中提供指导 - 之外,还会向您提供有关您是否 完成工作的反馈。请记住那句古老的谚语:如果它不起作用,我现在就完成了。

在精益词汇中,测试是“必要的浪费” - 它们不提供任何直接价值。因此,艺术只是在写那些提供间接价值的测试 - 通过帮助我们对我们生产的实际工作的信心充满信心。

因此,编写测试的最终指南应该是您对生产代码的置信度。这就是极限编程口号“测试可能破坏的所有内容”的来源 - 如果有可能破坏,我们需要通过测试作为我们的安全网,以便能够通过充满信心的重构在未来快速行动。如果某些东西“不可能破坏”(通常说简单的访问者),那么为它编写测试将是完全浪费。

当然,您的评估不时会失败。您需要经验才能找到合适的平衡点。最重要的是,每当您收到针对您的代码的错误报告时,您应该考虑哪种类型的测试可以防止此错误进入野外,并且将来会阻止类似的错误。然后将这种测试添加到您的代码“可能会破坏”的测试集合中。

答案 13 :(得分:0)

如果两个测试用例运行完全相同的代码,则无需单独测试它们。例如,对于读取配置文件的示例,您只需要测试它是否能够正确读取每种类型的值(并且当被要求读取不存在或无效的值时,它以正确的方式失败)。

如果你测试它正确读入配置文件中的每个值,那么你正在测试配置文件,而不是代码。

答案 14 :(得分:0)

如果您发现自己在测试程序中花费了所有调试时间,那么您可能已经过火了。

答案 15 :(得分:0)

TDD方法是关于设计 - 稍后进行一系列测试是一个相当受欢迎的副作用。测试驱动开发揭示了完全不同的代码,而不仅仅是“仅仅”作为事后想法的测试。

因此,当您在TDD环境中提出要求时:过度设计“过度设计”的解决方案很容易。当你的设计足够坚固以至于不会滑动时停止。无需用水泥固定,绑带和覆盖。您需要设计足够灵活,以便在下次重构时进行更改。

我过度设计测试的个人故事是一个过度模拟的测试,其中某些类的实现或多或少地反映在对各个模拟对象的'expect'调用中。谈谈采取改变要求的阻力......

答案 16 :(得分:0)

我认为this rule也应该适用于TDD以防止过度的单元测试。

答案 17 :(得分:0)

为了确定我对程序进行了多少测试工作,我根据要测试的内容定义了此测试活动的标准:代码的所有分支,所有功能,所有输入或输出域,所有功能......

鉴于此,我的测试工作在完全涵盖我的标准时完成。

我只需要知道某些目标无法达到,例如所有程序路径或所有输入值。