如何使用JUnit测试具有内部私有方法、字段或嵌套类的类?
仅仅为了能够运行测试而更改方法的访问修饰符似乎是不好的。
如何使用JUnit测试具有内部私有方法、字段或嵌套类的类?
仅仅为了能够运行测试而更改方法的访问修饰符似乎是不好的。
当前回答
您可以使用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
其他回答
如果您正在使用JUnit,请查看JUnit插件。它能够忽略Java安全模型并访问私有方法和属性。
私有方法由公共方法调用,因此公共方法的输入也应测试这些公共方法调用的私有方法。当公共方法失败时,这可能是私有方法的失败。
JML具有spec_public注释注释语法,允许您在测试期间将方法指定为public:
private /*@ spec_public @*/ int methodName(){
...
}
2.4隐私修改器和可见性中讨论了此语法。还有一个将JML规范转换为JUnit测试的程序。我不确定它的工作情况如何,或者它的功能是什么,但似乎没有必要,因为JML本身就是一个可行的测试框架。
在过去,我曾为Java使用过反射,在我看来这是一个很大的错误。
严格来说,您不应该编写直接测试私有方法的单元测试。您应该测试的是类与其他对象的公共契约;您不应该直接测试对象的内部。如果另一个开发人员想要对类进行一个小的内部更改,这不会影响类的公共契约,那么他/她就必须修改基于反射的测试,以确保它正常工作。如果在整个项目中重复这样做,那么单元测试就不再是代码健康状况的有用度量,而开始成为开发的障碍,成为开发团队的烦恼。
相反,我建议使用一个代码覆盖工具,例如Cobertura,以确保您编写的单元测试在私有方法中提供代码的适当覆盖。通过这种方式,您可以间接测试私有方法正在做什么,并保持更高级别的灵活性。
这是我的龙目样本:
public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Student student = new Student();
Field privateFieldName = Student.class.getDeclaredField("name");
privateFieldName.setAccessible(true);
privateFieldName.set(student, "Naruto");
Field privateFieldAge = Student.class.getDeclaredField("age");
privateFieldAge.setAccessible(true);
privateFieldAge.set(student, "28");
System.out.println(student.toString());
Method privateMethodGetInfo = Student.class.getDeclaredMethod("getInfo", String.class, String.class);
privateMethodGetInfo.setAccessible(true);
System.out.println(privateMethodGetInfo.invoke(student, "Sasuke", "29"));
}
@Setter
@Getter
@ToString
class Student {
private String name;
private String age;
private String getInfo(String name, String age) {
return name + "-" + age;
}
}