我一直在想单元测试的存根的一般用法与使用真实(生产)的实现,特别是当我们在使用存根时没有碰到一个相当讨厌的问题,如下所示:测试:stub vs真正的实现
假设我们有这样的(伪)代码:
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()
}
}
现在假设我们在某些时候,当我们已经写了数百次试验更晚,意识到,真的不应该扔AException而是AOtherException。我们纠正这一点,
public class A {
public int getInt() {
if (..) {
return 2;
}
else {
throw new AOtherException();
}
}
}
我们现在已经改变了的实施扔AOtherException然后我们运行我们的所有测试。他们通过。不好的是B的单元测试通过但错误。如果我们在此阶段将A和B放在生产中,则B将传播AOtherException,因为它的实现认为A引发AException。
如果我们改用A的throwsBExceptionWhenFailsToReadInt测试,那么它会在A发生变化后失败,因为B不会再抛出BException。
这只是一个可怕的想法,如果我们拥有的结构类似于上面的例子测试千元,我们改变了一个很小的事情,那么所有的单元测试仍然会运行,尽管许多单位的行为是错误的!我可能会错过一些东西,我希望你们中有些聪明的人能够启发我,知道它是什么。
测试代码也必须保留,这是不幸的事实 – BrokenGlass 2012-03-14 19:24:29
是真的。但是,正如我所看到的那样,如果上述问题随时出现,您就没有真正的机会维护一个带有数百个测试的大型测试平台。您当然不会记得更改测试代码的位置,因此似乎必须有另一种方式才能更轻松地发现此“错误类型”,因为它似乎是一个非常合理的场景。我觉得我错过了一些东西。我没看见的东西。关于集成测试的事情可能是?如果我们在B的测试中使用A的真实实现,我们会立即发现问题,正如我在帖子中提到的那样。 – johnrl 2012-03-14 19:39:57
如果你将两个类连接起来,并且他们之间做了很多逻辑,那么你可能会发现你的uniut测试非常难以编写,因为只是让所有事情处于正确的状态是一种真正的痛苦。事实上,你希望编写松散耦合的测试,以便依赖项的更改不会中断未测试的测试。你应该有一个测试来测试一件事情,因此理论上你只有一个测试来测试捕捉并抛出异常。如果您在整个应用程序中发现行为错乱,那么您希望所有这些测试都开始失败。 – aqwert 2012-03-14 20:17:48