如何使用JUnit测试具有内部私有方法、字段或嵌套类的类?
仅仅为了能够运行测试而更改方法的访问修饰符似乎是不好的。
如何使用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任务中的内部类。另一个问题。
其他回答
如果您试图测试您不愿意或无法更改的现有代码,则反射是一个不错的选择。
如果类的设计仍然是灵活的,并且您有一个复杂的私有方法需要单独测试,我建议您将其导出到一个单独的类中,并单独测试该类。这不需要更改原始类的公共接口;它可以在内部创建helper类的实例并调用helper方法。
如果您想测试来自helper方法的困难错误条件,可以更进一步。从helper类中提取一个接口,向原始类中添加一个公共getter和setter以注入helper类(通过其接口使用),然后将helper类的模拟版本注入原始类中,以测试原始类如何响应helper的异常。如果您想测试原始类而不同时测试助手类,这种方法也很有用。
在使用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());
}
}
如果您确实需要直接测试私有方法/类等,那么可以使用其他答案中已经提到的反射。然而,如果说到这一点,我宁愿使用框架提供的实用程序类,而不是直接处理反射。例如,对于Java,我们有:
Spring框架中的ReflectionUtilsJUnit中的ReflectionUtils
关于如何使用它们,你可以在网上找到很多文章。我特别喜欢这个:
org.springframework.util.ReflectionUtils的Java代码示例
我认为大多数答案都太粗糙了。这取决于您的代码是否应该测试私有方法。
我过去使用了私人方法测试,我是通过反思来完成的。它对我有用。我意识到它的问题,但对我来说,这是最好的解决方案。
我有一个强大的应用程序,可以模拟100多万人口中的人类行为。每个人都由一个对象表示。该应用程序的主要目的是跟踪某些东西(疾病、信息、思想)如何在人群中传播。
为此,我有方法将疾病或信息从一个对象传递给另一个对象。绝对没有理由公开这些方法,因为最终用户对人与人之间的一次传递不感兴趣。最终用户只对它如何在人群中传播的全貌感兴趣。所以这些方法是私人的。
但我想肯定的是,从一个人向另一个人传递一点信息的行为是否在做它应该做的事情。通过公共用户界面测试它也是不可能的,因为它只是不公开,我认为仅仅为了测试而公开它是很尴尬的。应用程序的最终输出由一直执行的数亿个这样的单个步骤定义。我也不能测试最终输出,因为这涉及到复杂的随机性,这使得它无法预测。
因此,我测试应用程序的方法是测试从一个人向另一个人传递信息的单个步骤。这些都是私人方法。所以我用反思来检验这一点。
我讲这个故事是为了表明这不是一个简单的黑白故事。这取决于您的应用程序。在某些情况下,通过反射测试私有方法可能是最好的选择。
如果这里的一些人在我的用例中知道更好的解决方案,我当然乐意接受纠正。。。。
测试私有方法的最佳方法是通过另一个公共方法。如果无法做到这一点,则以下条件之一为真:
私有方法是死代码您正在测试的类附近有设计气味您尝试测试的方法不应是私有的