如何测试私有函数或具有私有方法,字段或内部类的类?

如何对具有内部私有方法,字段或嵌套类的类进行单元测试(使用 xUnit)?还是通过内部链接 (在 C / C ++ 中为static )或在私有( 匿名 )名称空间中使其私有化的函数?

仅仅为了能够运行测试而更改方法或函数的访问修饰符似乎很糟糕。

答案

更新:

大约十年后,测试私有方法或任何无法访问的成员的最佳方法可能是来自Manifold框架的@Jailbreak

@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;

这样,您的代码将保持类型安全和可读性。没有设计折衷,也没有为了测试而过度曝光的方法和字段。

如果您使用的是旧版Java应用程序,并且不允许更改方法的可见性,那么测试私有方法的最佳方法是使用Reflection

在内部,我们使用助手来获取 / 设置privateprivate static变量,以及调用privateprivate static方法。以下模式将使您几乎可以执行与私有方法和字段相关的所有操作。当然,您不能通过反射更改private static 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);

笔记:
1. TargetClass.getDeclaredMethod(methodName, argClasses)使您可以查看private方法。同样的事情适用于getDeclaredField
2. 必须使用setAccessible(true)才能与私人玩耍。

测试私有方法的最佳方法是通过另一个公共方法。如果不能这样做,则满足以下条件之一:

  1. 私有方法是无效代码
  2. 您正在测试的班级附近有设计异味
  3. 您尝试测试的方法不应为私有

当我的类中有私有方法非常复杂时,我觉得有必要直接测试私有方法,这就是代码的味道:我的类太复杂了。

解决这些问题的常用方法是挑逗一个包含有趣内容的新类。通常,此方法及其与之交互的字段,也许可以将另一两种方法提取到新类中。

新类将这些方法公开为 “public”,因此可用于单元测试。现在,新类和旧类都比原始类更简单,这对我来说很棒(我需要使事情保持简单,否则我会迷路!)。

请注意,我并不是在建议人们不要动脑筋来创建类!这里的重点是利用单元测试的力量来帮助您找到好的新类。

过去,我曾使用反射来为 Java 执行此操作,我认为这是一个很大的错误。

严格来说,你应该编写单元测试直接测试私有方法。您应该测试的是该类与其他对象之间的公共合同;您永远不要直接测试对象的内部。如果另一个开发人员想要对该类进行小的内部更改,而不影响该类的公共合同,则他 / 她必须修改您基于反射的测试以确保它可以工作。如果您在整个项目中重复执行此操作,则单元测试将不再是对代码运行状况的有用衡量,并开始成为开发的障碍和开发团队的烦恼。

我建议做的是使用诸如 Cobertura 之类的代码覆盖工具,以确保您编写的单元测试以私有方法提供对代码的适当覆盖。这样,您可以间接测试私有方法的功能,并保持更高的敏捷性。

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

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

通常,单元测试旨在行使班级或单元的公共界面。因此,私有方法是您不希望显式测试的实现细节。

我想在哪里测试私有方法的两个示例:

  1. 解密例程 - 我不想让任何人看到它们只是为了进行测试,否则任何人都可以使用它们解密。但是它们是代码固有的,复杂的并且需要始终运行(明显的例外是反射,当未配置SecurityManager来防止这种情况时,反射在大多数情况下甚至可以用来查看私有方法)。
  2. 创建一个供社区使用的 SDK 。在这里 public 具有完全不同的含义,因为这是全世界可能看到的代码(不仅仅是我的应用程序内部的代码)。如果我不希望 SDK 用户看到它,我会将代码放入私有方法中 - 我不认为这是代码的味道,只是因为 SDK 编程的工作原理。但是当然,我仍然需要测试我的私有方法,而这些正是我的 SDK 功能真正存在的地方。

我了解仅测试 “合同” 的想法。但我看不出有人可以倡导实际上不测试代码 - 您的工作量可能会有所不同。

因此,我的权衡包括通过反射使 JUnit 复杂化,而不是损害我的安全性和 SDK。

私有方法由公共方法调用,因此,公共方法的输入也应测试由那些公共方法调用的私有方法。当公用方法失败时,则可能是专用方法失败。

我使用的另一种方法是将私有方法更改为打包私有或受保护的程序,然后用 Google Guava 库的@VisibleForTesting批注对其进行补充。

这将告诉使用此方法的任何人要当心,即使在包中也不要直接访问它。同样,测试类不必在物理上位于同一包中,而是在test文件夹下的同一包中。

例如,如果要测试的方法在src/main/java/mypackage/MyClass.java则您的测试调用应放在src/test/java/mypackage/MyClassTest.java 。这样,您就可以访问测试类中的测试方法。

与大型和古怪的类测试的遗留代码,它往往是能够测试一个私人(或公共)方法我正在写,现在非常有帮助。

我将junitx.util.PrivateAccessor -package用于 Java。访问私有方法和私有字段的大量有用的一线工具。

import junitx.util.PrivateAccessor;

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