2013-10-31 74 views
6

我有一个名为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"); 
    } 

} 

谢谢!

+0

对于这种情况,使该方法包私人即“默认”访问符。我使用EasyMock#createMockBuilder()创建部分模拟,所以我可以模拟该私有方法。不知道你的JMockit的等价物。 – deepakraut

+0

您对Easymock - Powermock解决方案感兴趣吗?否则,我想写出来给你。 –

+0

参见http://stackoverflow.com/questions/250692/how-do-you-unit-test-private-methods – Raedwald

回答

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)); 
    } 
} 
+0

谢谢你这回答我的问题!我在这里使用了第一个选项,尽管我完全同意不建议嘲笑私有方法。在我的情况下,如果没有调用几个服务的遗留代码链,就不能实例化'Something'对象。由于'methodB'检查'thing1'和'thing2'上特定字段的值,因此只需简单地模拟'methodB'并强制执行所需的返回,而不是尝试模拟'Something'或'CustomObject'。 – Kingand

+0

不幸的是,自从JMockit 1.23以后,对于模拟私有方法的期望支持已经被移除了,您现在必须使用MockUp。 – dag

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 
    } 
} 

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

你也可以使用反射,但我会觉得这样做很脏。如果你真的想要解决方案,让我知道,我会添加到这个答案。

+0

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

-1

嘲笑的私有方法,你需要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

+0

你说得对,我提供了非私人方法的示例。但是这个链接可能有帮助。 http://stackoverflow.com/questions/7803944/how-to-mock-private-method-for-testing-using-powermock – Lifecube

3

不要试图嘲笑私有方法,即使你在搞权谋这样做用讽刺的工具。私人成员是实施细节,您应该可以自由更改。而是使用非私人API来锻炼课程。如果这很麻烦,考虑将麻烦的代码移到不同的类中(如果它不存在),并使用依赖注入来注入麻烦代码的模拟实现。

1
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(); 

} 

} 
相关问题