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

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


当前回答

我最近遇到了这个问题,并编写了一个名为Picklock的小工具,它避免了显式使用Java反射API的问题,两个示例:

通过Java反射调用方法,例如private void方法(String s)

Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");

通过Picklock调用方法,例如private void方法(String s)

interface Accessible {
  void method(String s);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

设置字段,例如私有BigInteger金额;-通过Java反射

Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));

设置字段,例如私有BigInteger金额;-由Picklock提供

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));

其他回答

如果您正在使用JUnit,请查看JUnit插件。它能够忽略Java安全模型并访问私有方法和属性。

在使用ExpectedException时,对Cem Catikka的答案进行了快速补充:

请记住,预期的异常将被包装在InvocationTargetException中,因此为了获得异常,必须抛出收到的InvocationTargetException的原因。类似于(在BizService上测试私有方法validateRequest()):

@Rule
public ExpectedException thrown = ExpectedException.none();

@Autowired(required = true)
private BizService svc;


@Test
public void testValidateRequest() throws Exception {

    thrown.expect(BizException.class);
    thrown.expectMessage(expectMessage);

    BizRequest request = /* Mock it, read from source - file, etc. */;
    validateRequest(request);
}

private void validateRequest(BizRequest request) throws Exception {
    Method method = svc.getClass().getDeclaredMethod("validateRequest", BizRequest.class);
    method.setAccessible(true);
    try {
        method.invoke(svc, request);
    }
    catch (InvocationTargetException e) {
        throw ((BizException)e.getCause());
    }
 }

PowerMock.Whitebox是我见过的最好的选项,但当我阅读它的源代码时,它会读取带有反射的私有字段,所以我想我有了答案:

使用PowerMock测试私有内部状态(字段),或仅进行反射,而无需引入其他独立性的开销对于私人方法:事实上,这个问题本身的赞成票,以及大量的评论和答案,表明这是一个非常并发和有争议的话题,无法给出适合每种情况的确切答案。我知道只有合同需要测试,但我们也需要考虑保险范围。事实上,我怀疑只有测试合约才能100%让一个类免于错误。私有方法是那些在定义数据的类中处理数据的方法,因此对其他类不感兴趣,因此我们不能简单地公开以使其可测试。我会尽量不去测试它们,但当你必须的时候,就去尝试,忘记这里的所有答案。你比互联网上的任何人都更了解自己的处境和限制。当您可以控制代码时,请使用它。经过考虑,但不要过度思考。


一段时间后,当我重新考虑时,我仍然相信这是真的,但我看到了更好的方法。

首先,Powermock.Whitebox仍然可用。

而且,Mockito Whitebox在v2之后被隐藏了(我可以在Whitebox中找到的最新版本是testImplementation“org.Mockito:Mockito core:1.10.19”),并且它一直是org.mockit.internal包的一部分,未来很可能会发生重大变化(请参阅本文)。所以现在我倾向于不使用它。

在Gradle/MMaven项目中,如果您定义了私有方法或字段,那么除了反射之外,没有任何其他方法可以访问它们,因此第一部分是正确的。但是,如果您将可见性更改为“包私有”,则测试包中遵循相同结构的测试将可以访问它们。这也是我们被鼓励在主包和测试包中创建相同层次结构的另一个重要原因。因此,当您可以控制生产代码和测试时,删除私有访问修饰符可能是您的最佳选择,因为相对而言,它不会造成巨大影响。这使得测试和私人方法间谍成为可能。

@Autowired
private SomeService service; // With a package private method "doSomething()"

@Test
void shouldReturnTrueDoSomething() {
    assertThat(doSomething(input), is(true)); // Package private method testing
}

@Test
void shouldReturnTrueWhenServiceThrowsException() {
    SomeService spy = Mockito.spy(service); // Spying real object
    doThrow(new AppException()).when(spy).doSomething(input); // Spy package private method
    ...

}

谈到内部字段,在Spring中有ReflectionUtils.setField()。

最后,有时我们可以绕过问题本身:如果需要满足覆盖要求,也许可以将这些私有方法移到内部静态类中,并忽略Jacobo中的这个类。我只是找到了一些方法来忽略Jacobo Gradle任务中的内部类。另一个问题。

从本文:使用JUnit和SuiteRunner测试私有方法(Bill Venners)中,您基本上有4个选项:

不要测试私有方法。授予方法包访问权限。使用嵌套测试类。使用反射。

在C++中:在包含包含要测试的私有函数的类头之前。

使用此代码:

#define private public
#define protected public