在同一类中模拟被测试的私有方法

19

我有一个名为MyClass的Java类,我想用JUnit测试它。我要测试的公共方法methodA调用同一类中的私有方法methodB来确定要遵循哪个条件路径。我的目标是为methodA中的不同路径编写JUnit测试。此外,methodB调用了一个服务,因此我不希望在运行JUnit测试时实际执行它。

最好的方法是如何模拟methodB并控制其返回值,以便我可以测试“methodA”的不同路径?

我喜欢在编写模拟对象时使用JMockit,因此我特别感兴趣的是适用于JMockit的任何答案。

这是我的示例类:

public class MyClass  {

    public String methodA(CustomObject object1, CustomObject object2)  {

        if(methodB(object1, object2))  {
            // Do something.
            return "Result";
        }

        // Do something different.
        return "Different Result";

    }

    private boolean methodB(CustomObject custObject1, CustomObject custObject2)  {

        /* For the sake of this example, assume the CustomObject.getSomething()
         * method makes a service call and therefore is placed in this separate
         * method so that later an integration test can be written.
         */
        Something thing1 = cobject1.getSomething();
        Something thing2 = cobject2.getSomething();

        if(thing1 == thing2)  {
            return true;
        }
        return false;
    }

}

这是我目前为止的内容:

public class MyClassTest  {
    MyClass myClass = new MyClass();

    @Test
    public void test_MyClass_methodA_enters_if_condition()  {
        CustomObject object1 = new CustomObject("input1");
        CustomObject object2 = new CustomObject("input2");

        //  How do I mock out methodB here to return true?

        assertEquals(myClass.methodA(object1, object2), "Result");
    }

    @Test
    public void test_MyClass_methodA_skips_if_condition()  {
        CustomObject object1 = new CustomObject("input1");
        CustomObject object2 = new CustomObject("input2");

        //  How do I mock out methodB here to return false?

        assertEquals(myClass.methodA(object1, object2), "Different Result");
    }

}

谢谢!


对于这种情况,将该方法设置为包私有即“默认”访问限定符。我使用EasyMock#createMockBuilder()来创建部分模拟,以便可以模拟该私有方法。不知道你们JMockit的等效方法。 - deepakraut
你对Easymock-Powermock解决方案感兴趣吗?否则我可以为您编写它。 - Tom Jonckheere
请参见https://dev59.com/6nVC5IYBdhLWcg3wjyDu。 - Raedwald
请参见https://dev59.com/bHVD5IYBdhLWcg3wRpaX。 - Raedwald
5个回答

2

根据您的要求提供答案(使用JMockit的部分模拟):

public class MyClassTest
{
    @Tested MyClass myClass;

    @Test
    public void test_MyClass_methodA_enters_if_condition() {
        final CustomObject object1 = new CustomObject("input1");
        final CustomObject object2 = new CustomObject("input2");

        new NonStrictExpectations(myClass) {{
            invoke(myClass, "methodB", object1, object2); result = true;
        }};

        assertEquals("Result", myClass.methodA(object1, object2));
    }

    @Test
    public void test_MyClass_methodA_skips_if_condition() {
        final CustomObject object1 = new CustomObject("input1");
        final CustomObject object2 = new CustomObject("input2");

        new NonStrictExpectations(myClass) {{
            invoke(myClass, "methodB", object1, object2); result = false;
        }};

        assertEquals("Different Result", myClass.methodA(object1, object2));
    }
}

然而,我不建议这样做。一般来说,不应该模拟private方法。相反,应该模拟您正在测试的单元的实际外部依赖项(在本例中为CustomObject):

public class MyTestClass
{
    @Tested MyClass myClass;
    @Mocked CustomObject object1;
    @Mocked CustomObject object2;

    @Test
    public void test_MyClass_methodA_enters_if_condition() {
        new NonStrictExpectations() {{
            Something thing = new Something();
            object1.getSomething(); result = thing;
            object2.getSomething(); result = thing;
        }};

        assertEquals("Result", myClass.methodA(object1, object2));
    }

    @Test
    public void test_MyClass_methodA_skips_if_condition() {
        new NonStrictExpectations() {{
            object1.getSomething(); result = new Something();
            object2.getSomething(); result = new Something();
        }};

        assertEquals("Different Result", myClass.methodA(object1, object2));
    }
}

谢谢!这解答了我的问题。我使用了第一个选项,尽管我完全认同模拟私有方法并不推荐。在我的情况下,“Something”对象不能在没有调用几个服务的遗留代码链的情况下实例化。由于“methodB”检查“thing1”和“thing2”的特定字段的值,所以更容易模拟出“methodB”并强制返回所需结果,而不是尝试模拟“Something”或“CustomObject”。 - Kingand
很遗憾,自JMockit 1.23版本起,对于模拟私有方法的Expectations支持已被移除,现在您必须使用MockUp。 - dag

2

不要试图嘲笑私有方法,即使您可以使用模拟工具进行欺骗。私有成员是实现细节,您应该自由更改。相反,请使用非私有API来练习类。如果这很麻烦,请考虑将棘手的代码移动到另一个类中(如果尚未存在),并使用依赖注入来注入棘手代码的模拟实现。


0
import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ MyClass.class })
public class MyClassTest {

// Class Under Test
MyClass cut;

@Before
public void setUp() {

    // Create a new instance of the service under test (SUT).
    cut = new MyClass();

    // Common Setup
    // TODO
}

@Test
public void testMethodA() throws Exception {

    /* Initialization */
    CustomObject object2 = PowerMock.createNiceMock(CustomObject.class);
    CustomObject object1 = PowerMock.createNiceMock(CustomObject.class);

    MyClass partialMockCUT = PowerMock.createPartialMock(MyClass.class,
            "methodB");
    long response = 1;

    /* Mock Setup */
    PowerMock
            .expectPrivate(partialMockCUT, "methodB",
                    EasyMock.isA(CustomObject.class),
                    EasyMock.isA(CustomObject.class)).andReturn(true)
            .anyTimes();

    /* Mock Setup */

    /* Activate the Mocks */
    PowerMock.replayAll();

    /* Test Method */

    String result = partialMockCUT.methodA(object1, object2);

    /* Asserts */
    Assert.assertNotNull(result);
    PowerMock.verifyAll();

}

}

0
将 methodB 设为一个独立类的成员,并在 MyClass 中拥有该类的私有引用。
public class MyClass  {
    private MyOtherClass otherObject = new MyOtherClass();

    public String methodA(CustomObject object1, CustomObject object2)  {

        if(otherObject.methodB(object1, object2))  {
            // Do something.
            return "Result";
        }

        // Do something different.
        return "Different Result";

    }
}

class MyOtherClass {
    public boolean methodB(CustomObject custObject1, CustomObject custObject2)  {
        // Yada yada code
    }
}

个人而言,我通常只测试公共方法并查看覆盖率报告以确保所有路径都已访问我的私有方法。如果我真的需要测试一个私有方法,那就是需要重构的信号,就像我上面所说的。

你也可以使用反射,但我觉得这样做很不好。如果你真的想要解决方案,请告诉我,我会将其添加到这个答案中。


我的示例代码可能会误导。我的目标是测试methodA//Do Something部分的功能。我正在寻找一种方法来强制methodA进入if语句,以便我可以测试其中的代码。我同意,如果我试图测试methodB,那么这将表明我可能需要重构。 - Kingand

-2

要模拟私有方法,需要使用powermock
示例代码如下,但我还没有运行过。

    import org.mockito.Mockito;
    import org.powermock.api.mockito.PowerMockito;
    import org.powermock.modules.junit4.PowerMockRunner;

    @RunWith (PowerMockRunner.class)
    public class MyClassTest  {

        @Test
        public void test_MyClass_methodA_enters_if_condition()  {
            final MyClass myClass = Mockito.mock (MyClass.class);
            CustomObject object1 = new CustomObject("input1");
            CustomObject object2 = new CustomObject("input2");
            Mockito.when (myClass.methodB(object1, object2)).thenReturn (true);
            Mockito.when (myClass.methodA(object1, object2)).thenCallRealMethod ();

            assertEquals(myClass.methodA(object1, object2), "Result");
        }
    }

1
我觉得这段代码不会编译通过。编译器会抱怨methodB()是私有的。在PowerMock中还有其他方法可以调用带有字符串参数的私有方法。虽然这样做有点丑陋。 - mikeslattery
你说得对,我提供的是非私有方法的示例。但这个链接可能会有所帮助。https://dev59.com/SGsz5IYBdhLWcg3wlYwn - Lifecube

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接