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

Unit testing 使用模拟对象编写可维护的单元测试,unit-testing,mocking,easymock,Unit Testing,Mocking,Easymock,这是一个类的简化版本,我正在为其编写单元测试 class SomeClass { void methodA() { methodB(); methodC(); methodD(); } void methodB() { //does something } void methodC() { //does something } void methodD(

这是一个类的简化版本,我正在为其编写单元测试

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,这意味着我必须以不同的期望多次设置模拟对象


最后,我的单元测试变得很难维护,而且非常混乱。我想知道是否有人已经或看到了解决这个问题的好方法。

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

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

这是一个示例,因为模拟设置对该框架的了解太多


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

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

在您的情况下,方法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
    }
}

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

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

设置相关上下文,验证结果,并使用nicemock进行其他操作

我更喜欢Mockito(Java)或Moq(.NET),默认情况下它们是这样工作的。这里是Mockito与EasyMock的页面,您可以了解Mockito与EasyMock的对比(在Mockito出现之前,EasyMock没有NiceMock):

您可能可以以类似的方式使用EasyMock的NiceMock。希望这能帮助你确定你的测试。您始终可以导入这两个框架并将它们一起使用/如果有帮助,可以增量切换

祝你好运

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

如果您关心methodA在做什么以及必须调用哪个collaborator函数,那么您必须设置不同的期望。。。我不明白你怎么能跳过这一步

如果您testLogout,您将期望调用myCollaborator.logout(),否则如果您testLogin,您将期望类似myCollaborator.login()的内容

如果你有很多方法,有很多/不同的期望值,那么可能需要将你的类分成协作者