【发布时间】:2012-03-14 19:22:41
【问题描述】:
我一直想知道在单元测试中一般使用存根与使用真实(生产)实现,特别是在使用存根时我们是否不会遇到一个相当讨厌的问题,如下所示:
假设我们有这个(伪)代码:
public class A {
public int getInt() {
if (..) {
return 2;
}
else {
throw new AException();
}
}
}
public class B {
public void doSomething() {
A a = new A();
try {
a.getInt();
}
catch(AException e) {
throw new BException(e);
}
}
}
public class UnitTestB {
@Test
public void throwsBExceptionWhenFailsToReadInt() {
// Stub A to throw AException() when getInt is called
// verify that we get a BException on doSomething()
}
}
现在假设我们稍后再编写数百个测试时,意识到 A 不应该真的抛出 AException,而是应该抛出 AOtherException。我们更正了这一点:
public class A {
public int getInt() {
if (..) {
return 2;
}
else {
throw new AOtherException();
}
}
}
我们现在更改了 A 的实现以抛出 AOtherException,然后我们运行所有的测试。他们通过。不好的是 B 的单元测试通过但错误。如果我们在这个阶段将 A 和 B 放在一起生产,B 将传播 AOtherException,因为它的实现认为 A 抛出 AException。
如果我们在 throwsBExceptionWhenFailsToReadInt 测试中使用 A 的真实实现,那么在更改 A 后它会失败,因为 B 不会再抛出 BException。
一个可怕的想法是,如果我们有上千个测试结构像上面的例子,并且我们改变了一点点,那么即使许多单元的行为是错误的,所有的单元测试仍然会运行!我可能遗漏了一些东西,我希望你们中的一些聪明人能告诉我它是什么。
【问题讨论】:
-
测试代码也必须维护,这是不幸的事实
-
是的。但在我看来,如果上述问题随时发生,你就没有真正的机会维护一个包含数百个测试的大型测试平台。你肯定不会记得在哪里更改测试代码,所以似乎必须有另一种方法来更容易地发现这种“错误类型”,因为这似乎是一个非常合理的场景。我觉得我错过了什么。我看不到的东西。也许是关于集成测试的东西?如果我们在 B 的测试中使用 A 的真实实现,我们会立即发现问题,正如我在帖子中提到的那样。
-
如果你确实将这两个类结合在一起,并且它们之间做了很多逻辑,那么你可能会发现你的 uniut 测试很难编写,因为只是让所有东西都处于正确的状态真的很痛苦。事实上,您希望编写松散耦合的测试,以便依赖项的更改不会破坏您未测试的测试。您应该有一个测试来测试一件事,因此理论上您将只有一个测试来测试捕获和抛出该异常。如果您在整个应用程序中都存在这种行为变化,那么您希望所有这些测试都开始失败。
标签: unit-testing testing tdd integration-testing