单元测试 - 合同变更的单元测试的好处?

Ste*_*iks 51 unit-testing design-by-contract

最近我与一位同事就单元测试进行了一次有趣的讨论.当合同发生变化时,我们正在讨论何时维持单元测试的效率降低.

也许任何人都可以告诉我如何解决这个问题.让我详细说明:

所以我们假设有一个类可以做一些漂亮的计算.合同说它应该计算一个数字,或者当它由于某种原因失败时返回-1.

我有合同测试谁测试.在我所有的其他测试中,我将这个漂亮的计算器存在.

所以现在我改变了契约,只要它无法计算就会抛出一个CannotCalculateException.

我的合同测试将失败,我将相应地修复它们.但是,我所有的模拟/存根对象仍将使用旧的合同规则.这些测试将成功,而他们不应该!

提出的问题是,凭借对单元测试的这种信念,可以对这些变化有多少信心......单元测试成功,但在测试应用程序时会出现错误.使用这个计算器的测试需要修复,这需要花费时间,甚至可能很多时候被抄袭/嘲笑......

你觉得这个案子怎么样?我从来没有想过它.在我看来,单元测试的这些变化是可以接受的.如果我不使用单元测试,我也会在测试阶段(测试人员)看到这样的错误.然而,我没有足够的信心指出什么会花费更多的时间(或更少).

有什么想法吗?

小智 92

你提出的第一个问题是所谓的"脆弱测试"问题.您对应用程序进行了更改,并且由于此更改而导致数百个测试中断.发生这种情况时,您遇到了设计问题.您的测试设计为脆弱的.它们与生产代码没有充分分离.解决方案(就像在所有软件问题中一样)找到一个抽象,将测试与生产代码分离,使生产代码的波动性从测试中隐藏.

一些导致这种脆弱性的简单事物是:

  • 测试显示的字符串.这样的字符串是不稳定的,因为它们的语法或拼写可能会随着分析师的心血来潮而改变.
  • 测试应在抽象后面编码的离散值(例如3)(例如FULL_TIME).
  • 从许多测试中调用相同的API.您应该将API调用包装在测试函数中,以便在API更改时可以在一个位置进行更改.

测试设计是TDD初学者经常忽视的重要问题.这通常导致脆弱的测试,然后导致新手拒绝TDD为"非生产性".

你提出的第二个问题是误报.你已经使用了很多模拟,你的测试都没有真正测试集成系统.虽然测试独立单元是一件好事,但测试系统的部分和整体集成也很重要.TDD 只是单元测试.

测试应安排如下:

  • 单元测试提供接近100%的代码覆盖率.他们测试独立单位.它们由程序员使用系统的编程语言编写.
  • 组件测试覆盖了系统的约50%.它们由业务分析师和QA编写.它们用FitNesse,Selenium,Cucumber等语言编写.它们测试整个组件,而不是单个单元.他们主要测试快乐路径案例和一些高度可见的不快乐路径案例.
  • 集成测试覆盖了系统的约20%.他们测试组件的小组件而不是整个系统.也写于FitNesse/Selenium/Cucumber等.由建筑师撰写.
  • 系统测试覆盖系统的约10%.他们测试整个系统集成在一起.它们也是用FitNesse/Selenium/Cucumber等编写的.由建筑师撰写.
  • 探索性手动测试.(参见James Bach)这些测试是手动的,但没有编写脚本.他们运用人类的聪明才智和创造力.

  • 我认为你在原始问题中实际上错过了一件事:当在测试套件中反复创建相同类型的模拟时,它确实应该被分解到一个地方.在测试中所有代码重复仍然是代码重复并且仍然很糟糕. (6认同)

b.r*_*oth 12

最好必须修复因故意代码更改而失败的单元测试,而不是没有测试来捕获这些更改最终引入的错误.

当您的代码库具有良好的单元测试覆盖率时,您可能会遇到许多单元测试失败,这些失败不是由于代码中的错误,而是对合同或代码重构的有意更改.

但是,该单元测试覆盖率还将使您有信心重构代码并实施任何合同更改.某些测试将失败并需要修复,但由于您在这些更改中引入的错误,其他测试最终会失败.


Pét*_*rök 5

即使在100%代码/功能覆盖的理想情况下,单元测试肯定无法捕获所有错误.我认为这是不可预期的.

如果测试合同发生变化,我(开发人员)应该使用我的大脑相应地更新所有代码(包括测试代码!).如果我无法更新一些因此仍会产生旧行为的模拟,那就是我的错,而不是单元测试.

它类似于我修复bug并为其生成单元测试的情况,但我没有考虑(并测试)所有类似的情况,其中一些后来证明也是错误的.

所以,是的,单元测试需要维护以及生产代码本身.没有维护,它们会腐烂和腐烂.