如何使用JUnit测试具有内部私有方法、字段或嵌套类的类?

仅仅为了能够运行测试而更改方法的访问修饰符似乎是不好的。


当前回答

今天,我推出了一个Java库来帮助测试私有方法和字段。它的设计考虑到了Android,但它确实可以用于任何Java项目。

如果您有一些带有私有方法、字段或构造函数的代码,可以使用BoundBox。它正是你想要的。下面是一个测试示例,它访问Android活动的两个私有字段来测试它:

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

BoundBox使测试私有/受保护的字段、方法和构造函数变得容易。你甚至可以访问被继承隐藏的东西。实际上,BoundBox打破了封装。它会让您通过反射访问所有这些内容,但在编译时会检查所有内容。

它非常适合测试一些遗留代码。小心使用。;)

其他回答

还有另一种方法可以测试您的私有方法。

如果在运行配置中“启用断言”,则可以在方法本身内部对方法进行单元测试。例如

assert ("Ercan".equals(person1.name));
assert (Person.count == 2);

如果您有一些遗留的Java应用程序,并且不允许更改方法的可见性,那么测试私有方法的最佳方法是使用反射。

在内部,我们使用助手来获取/设置私有和私有静态变量,以及调用私有和私有的静态方法。以下模式将允许您执行与私有方法和字段相关的任何操作。当然,您不能通过反射来更改私有静态final变量。

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

对于字段:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

笔记:TargetClass.getDeclaredMethod(methodName,argClasses)允许您查看私有方法。同样的事情也适用于getDeclaredField。需要setAccessible(true)才能与private一起玩。

测试私有方法的最佳方法是通过另一个公共方法。如果无法做到这一点,则以下条件之一为真:

私有方法是死代码您正在测试的类附近有设计气味您尝试测试的方法不应是私有的

要用大型而古怪的类测试遗留代码,能够测试我现在正在编写的一个私有(或公共)方法通常非常有用。

我在Java中使用junitx.util.PrivateAccessor包。它有很多用于访问私有方法和私有字段的有用的一行程序。

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);

您可以使用PowerMockito为要测试的私有方法中调用/使用的私有字段和私有方法设置返回值:

例如,为私有方法设置返回值:

MyClient classUnderTest = PowerMockito.spy(new MyClient());

// Set the expected return value
PowerMockito.doReturn(20).when(classUnderTest, "myPrivateMethod", anyString(), anyInt());
// This is very important. Otherwise, it will not work
classUnderTest.myPrivateMethod();

// Setting the private field value as someValue:
Whitebox.setInternalState(classUnderTest, "privateField", someValue);

最后,您可以通过以下方式验证您的私有方法:

String msg = Whitebox.invokeMethod(obj, "privateMethodToBeTested", "param1");
Assert.assertEquals(privateMsg, msg);

Or

如果classUnderTest私有方法不返回值,但它设置了另一个私有字段,则可以获取该私有字段值以查看其设置是否正确:

// To get the value of a private field
MyClass obj = Whitebox.getInternalState(classUnderTest, "foo");
assertThat(obj, is(notNull(MyClass.class))); // Or test value