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

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


当前回答

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任务中的内部类。另一个问题。

其他回答

我最近遇到了这个问题,并编写了一个名为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));

Android具有Android.support.annotation包中的@VisibleForTesting注释。

@VisibleForTesting注释表明,一个带注释的方法比通常情况下使方法可测试所需的方法更可见。此注释有一个可选的otherwise参数,如果不需要使方法在测试中可见,则可以指定该方法的可见性。Lint使用otherwise参数来强制实现预期的可见性。

实际上,这意味着您应该打开一个方法进行测试,@VisibleForTesting注释将显示警告。

例如

package com.mypackage;

public class ClassA {

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    static void myMethod() {

    }
}

当您在同一个包(com.mypackage)中调用ClassA.myMethod()时,您将看到警告。

您可以关闭反射的Java访问限制,这样私有就没有什么意义了。

setAccessible(true)调用实现了这一点。

唯一的限制是ClassLoader可能不允许您这样做。

有关在Java中实现这一点的方法,请参阅颠覆单元测试的Java访问保护(Ross Burton)。

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

使用此代码:

#define private public
#define protected public

一般来说,单元测试旨在使用类或单元的公共接口。因此,私有方法是您不希望显式测试的实现细节。