使用模拟对象编写可维护的单元测试

时间:2010-06-29 08:20:36

标签: unit-testing mocking easymock

这是我正在为

编写单元测试的类的简化版本
class SomeClass {

    void methodA() {
        methodB();
        methodC();
        methodD();
    }

    void methodB() {
        //does something
    }

    void methodC() {
        //does something
    }

    void methodD() {
        //does something
    }
}

在为这个类编写单元测试时,我使用每种方法中使用的EasyMock模拟了对象。设置模拟对象和期望很容易 在方法B,C和D中。但是为了测试方法A,我必须设置更多的模拟对象及其期望。另外,我在不同的条件下测试方法A,这意味着我必须多次设置模拟对象,并有不同的期望。

最后,我的单元测试变得难以维护并且非常混乱。我想知道是否有人有或看到过这个问题的良好解决方案。

5 个答案:

答案 0 :(得分:3)

如果我理解你的问题,我认为这是一个设计问题。单元测试的好处在于编写测试通常会迫使您更好地进行设计。如果在测试方法时需要模拟太多东西,通常意味着您应该将类​​拆分为两个较小的类,这将更容易测试(以及编写和维护,以及错误修复和重用等)。

在您的情况下,方法A似乎比方法A,B,C更高级。您可以考虑将其移除到更高级别的类,这将包装SomeClass:

class HigherLevelClass {
    ISomeClass someClass;

    public HigherLevelClass(ISomeClass someClass)
    {
        this.someClass = someClass;
    }

    void methodA() {
        someClass.methodB();
        someClass.methodC();
        someClass.methodD();
    }
}

class SomeClass : ISomeClass {
    void methodB() {
        //does something
    }

    void methodC() {
        //does something
    }

    void methodD() {
        //does something
    }
}

现在当你测试方法时,你需要模拟的只是小的ISomeClass接口和三个方法调用。

答案 1 :(得分:0)

您可以将常见的设置代码提取到单独的(可能是参数化的)方法中,然后在适当的时候调用它们。如果methodA的测试与其他方法的测试具有非常不同的夹具,则可能没有太多内容放入@Before方法本身,因此您需要从测试方法本身调用设置帮助方法的适当组合。它仍然有点麻烦,但比在整个地方复制代码要好。

根据您使用的单元测试框架,可能还有其他选项,但上述内容适用于任何框架。

答案 2 :(得分:0)

这是Fragile test的一个示例,因为模拟设置对SUT有过深入的了解。

我不知道EasyMock,但是使用Moq你不需要设置void方法。但是,使用Moq,方法必须是公共的或受保护的和虚拟的。

答案 3 :(得分:0)

对于您正在编写的每个测试,请考虑对该测试有价值的行为。您将设置一些行为所依赖的上下文,以及您要验证的行为导致的某些结果。

设置相关的上下文,验证结果,并将NiceMocks用于其他所有内容。

我更喜欢Mockito(Java)或Moq(.NET),它默认以这种方式工作。这是关于Mockito与EasyMock的Mockito页面,所以你可以得到这个想法(EasyMock在Mockito出现之前没有NiceMock):

http://code.google.com/p/mockito/wiki/MockitoVSEasyMock

您可以以类似的方式使用EasyMock的NiceMock。希望这可以帮助您解决您的测试。您可以随时导入这两个框架并将它们一起使用/逐步切换,如果有帮助的话。

祝你好运!

答案 4 :(得分:0)

  

我在不同的条件下测试方法A,这意味着我必须多次设置模拟对象,并有不同的期望。

如果您关心A正在做什么方法以及必须调用哪个协作者功能,那么您必须设置不同的期望...我不知道您如何跳过此步骤?!

如果你是testLogout,你会期望调用myCollaborator.logout(),否则如果你使用testLogin,你会期望像myCollaborator.login()。

如果您有很多方法有很多/不同的期望,可能会在合作者中分割您的课程