2010-09-30 46 views
3

由于我的应用程序域的数据结构到最近变得非常复杂,我开始阅读模拟对象。很快出现了一个简单的问题,但迄今为止,答案已经证明是相当头痛的。所以这里有云:如何在单元测试中使用模拟手法避免误报?

我们有一类“富”与“酒吧”作为其中的一个方法:

class Foo { 
    public String bar(int i){ 
     if(i == 1) return "arrr!"; 
    } 
} 

而且我们有一个类海盗调用Foo.bar(1);在其中的一个方法:

class Pirate { 
    public String yell(){ 
     Foo foo = new Foo(); 
     return foo.bar(1); 
    } 

现在我们嘲笑Foo类的海盗类的单元测试,因为富恰好有其他依赖过多:

@Test 
public void returnsPirateString() { 
    Pirate blackBeard = new Pirate(); 
    Foo fooMock = mock(Foo.class); 
    fooMock.expectAndReturn("bar",1,"arrr!"); //expects 'bar' function to be called once and returns "arrr!" 
    assertEquals(blackBeard.yell(),"arrr!"); 
} 

现在会发生什么,是如果我们重构方法栏来返回null而不是“arrr!”,我们的测试将继续愉快地运行,而我们的程序不能按照我们想要的方式工作。这可能会导致可能的调试噩梦。

使用mockist方法而不是单元测试的经典测试方法,大多数时候所有“helper”对象都会被嘲笑,只有被测试的对象才会被解除锁定,所以前面提到的问题也会经常发生。

可以做什么来防止这个问题,而嘲笑?

回答

2

你应该单独测试'helper object'。一旦对这两者进行了覆盖和测试,那么您可以确定两者都以预期的方式相互影响。

更改您的'帮助对象'是应该与该帮助对象进行测试以确认其仍然按预期行为一样。

如果你关心帮助和初级班的组合的具体运行时的行为,那么你应该在更高层次上使用集成测试,或其他一些测试,如预期般一起断言两项工作。

+0

是的,但是这有点不利于嘲笑的使用,因为无论如何你必须创建你的数据结构来进行不同的测试。在这种情况下,我可以创建一个Object母体,并在这两个测试中使用这个母亲而不是嘲笑。 – tmetten 2010-09-30 14:45:02

+1

我不确定我是否遵循...嘲讽的要点是单独测试您的不同类,以便您可以专注于测试受测试的特定类。在测试帮助者时,你应该测试那个帮助者与其他真正的类完全隔离。 – Pete 2010-09-30 15:06:18

+0

是的,通过这样做,你的测试不会指出任何问题,而实际上课程会失败。 – tmetten 2010-09-30 15:17:10

3

在测试中,您正在测试使用Foo的Pirate类的yell()方法。所以你必须嘲笑Foo的酒吧方法的行为。 为了确保您的bar方法正常工作,您需要另一个测试用例来测试Foo的bar方法。

@Test 
public void testBar() { 
    //make sure bar retrun "arrr"! 
} 

现在如果你的bar方法返回null,这个测试用例会失败!

+0

是的,但是如果bar现在应该返回null呢? Foo的测试会失败,但所有其他单元测试与嘲笑Foo类不会抱怨,而这些测试可能会失败,而使用真实的对象,而不是嘲笑的。 – tmetten 2010-09-30 14:41:49

+1

当前酒吧的行为以某种方式测试时,你当前的测试用例测试了yell()。为了防止bar的不同行为,您需要更多的yell()测试用例,每个测试用例都会测试一下bar的每个行为。所以你会有像testYellWhenBarReturnsNormally(),testYellWhenBarThrowsException(),testYellWhenBarReturnsNull()这样的测试用例。我知道这听起来像很多工作,但您需要运用一些判断来确定您需要哪些测试用例! – Sasi 2010-09-30 14:56:43

+0

但是,然后你基本上是黑盒测试,而不是白盒测试... – tmetten 2010-09-30 14:57:39

0

测试returnsPirateString不是假阳性 - !这是在测试时PirateFoo实例返回‘arrr会发生什么’

换句话说,当你测试Pirate.yell不要紧什么Foo.bar回报,除非它创建了一个特殊的边界条件(和你应该已经有了一个测试,文档什么yell做时Foo返回null)。

Pirate.yell不负责保证对Foo.bar任何特定的返回值,所以它的单元测试不应该指望任何特定的回报values.You甚至应该改变你的测试使用其他的东西比的Foo.bar电流返回值的点。

+0

所以你说的是当我使用模拟对象我应该测试每个可能的特殊值/边界情况?另外请注意,Bar最初的设计是为了返回'arrr!'并突然被重构为返回null。你必须跟踪所有的单元测试,根据原始定义,这个函数被嘲弄,并将它们改为重构的定义... – tmetten 2010-09-30 14:55:48

+0

不,你只需编写足够的单元测试来覆盖有趣的情况。 'Pirate'不是保证'Foo.bar'返回值的业务(除非你通过向实际代码添加断言来完成它的部分职责)。我不会为任何**单元测试编写像示例中那样简单的外观方法。 – 2010-09-30 14:59:50

+0

当然不是,我只是想提供一个简单的点代码示例。 :D事实上,这并不是保证Foo.bar返回值的海盗业务,但实际上,由于Foo.bar的返回值,Pirate.yell()将失败。 – tmetten 2010-09-30 15:06:49

相关问题