如何testing具有私有方法,字段或内部类的类?

如何使用JUnittesting具有内部私有方法,字段或嵌套类的类?

为了能够运行一个testing,改变一个方法的访问修饰符似乎是不好的。

如果您有一些遗留的应用程序,并且不允许更改方法的可见性,那么testing私有方法的最好方法就是使用reflection 。

在内部,我们使用助手来获取/设置privateprivate staticvariables,以及调用privateprivate static方法。 以下模式将使您可以执行与私有方法和字段相关的任何事情。 当然,你不能通过reflection来改变private static finalvariables。

 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)需要和私人玩家一起玩。

testing私有方法的最好方法是通过另一种公共方法。 如果不能这样做,那么下列条件之一是正确的:

  1. 私有方法是死代码
  2. 你正在testing的class级附近有一种devise气味
  3. 您正在尝试testing的方法不应该是私人的

当一个类中的私有方法足够复杂,我觉得需要直接testing私有方法时,这是一种代码异味:我的类太复杂了。

我通常的解决这些问题的方法是挑出一个包含有趣的部分的新类。 通常情况下,这个方法及其交互的领域,也可能是另一个方法或两个方法,可以提取到一个新的类。

新类将这些方法公开为“公共”,因此它们可以用于unit testing。 现在的新课程和旧课程都比原来的课程简单,这对我来说很好(我需要保持简单,否则我会迷路)。

请注意,我并不是说人们不用脑子创造课堂! 这里的重点是用unit testing的力量来帮助你find好的新课程。

过去我用reflection做这个,我认为这是一个很大的错误。

严格地说,你应该编写直接testing私有方法的unit testing。 你应该testing的是类与其他对象之间的公共契约; 你不应该直接testing一个对象的内部。 如果另一位开发人员想要对class级做一个小的内部改变,而不影响class级的公共合同,那么他/她必须修改你的基于反思的testing,以确保它能正常工作。 如果你在整个项目中重复这样做,那么unit testing就会停止成为代码健康的一个有用的度量标准,并开始成为开发的障碍,并且会给开发团队带来麻烦。

我build议使用Cobertura这样的代码覆盖工具,以确保您编写的unit testing能够在私有方法中提供体面的代码覆盖。 这样,你间接testing私有方法在做什么,并保持更高的敏捷性。

从这篇文章: 使用JUnit和SuiteRunner (Bill Venners) testing私有方法 ,基本上有4个选项:

  • 不要testing私有方法。
  • 给方法包访问权限。
  • 使用嵌套的testing类。
  • 使用reflection。

一般来说,unit testing是为了锻炼一个class级或单元的公共接口。 因此,私有方法是你不希望明确testing的实现细节。

只是两个例子,我想testing一个私有方法:

  1. 解密例程 – 我不想让任何人只为了testing而看到它们,否则任何人都可以使用它们解密。 但是它们是代码的内在,复杂的,需要一直工作(当SecurityManager没有configuration为防止这种情况时,在大多数情况下,甚至可以使用reflection来查看私有方法)。
  2. 为社区消费创build一个SDK 。 在这里,公众具有完全不同的含义,因为这是全世界可以看到的代码(不仅仅是我的应用程序的内部)。 如果我不想让SDK用户看到它,我把代码放到私有方法中 – 我不认为这是代码味道,仅仅是SDK编程的工作原理。 但是当然,我仍然需要testing我的私有方法,而且它们是我的SDK的实际function所在。

我明白只testing“合同”的想法。 但是我没有看到人们可以主张实际上不testing代码 – 你的里程可能会有所不同。

所以我的权衡包括用reflection来复杂化JUnits,而不是妥协我的安全和SDK。

私有方法由公共方法调用,所以公共方法的input也应该testing这些公有方法调用的私有方法。 当一个公共方法失败时,那可能是私有方法的失败。

为了testing遗留代码,使用大而古怪的类,能够testing我正在编写的一个私有(或公共)方法通常是非常有帮助的。

我使用junitx.util.PrivateAccessor -package 。 许多有用的单线程访问私有方法和私人领域。

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

希望有帮助:)

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

这将告诉任何人使用这种方法要小心,不要直接访问它,即使在一个包中。 另外,testing类不需要在物理上处于相同的包中,而是在testing文件夹下的相同包中。

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

正如其他人所说…不要直接testing私有方法。 这里有一些想法:

  1. 保持所有的方法小而专注(易于testing,容易发现什么是错的)
  2. 使用代码覆盖工具。 我喜欢Cobertura (哦,快乐的一天,看起来像一个新的版本了!)

运行unit testing的代码覆盖率。 如果您发现方法没有完全testing,请添加到testing中以获取覆盖范围。 瞄准100%的代码覆盖率,但意识到你可能不会得到它。

私人方法被公共方法所消耗。 否则,他们是死代码。 这就是为什么你testing公共方法,断言公共方法的预期结果,从而它所消耗的私有方法。

在公共方法上运行你的unit testing之前,应该通过debugging来testing私有方法。

他们也可以使用testing驱动开发进行debugging,debugging你的unit testing,直到你所有的断言得到满足。

我个人认为使用TDD创build类更好; 创build公共方法存根(stub),然后用事先定义的所有断言生成unit testing,因此在编写代码之前确定方法的预期结果。 这样,你就不会走错了使unit testing断言符合结果的错误path。 你的课程是健壮的,并符合要求时,所有的unit testing通过。

试过Cem Catikkas的解决scheme后 ,我不得不说他的解决scheme比我在这里描述的更加优雅。 但是,如果您正在寻找使用reflection的替代方法,并且可以访问您正在testing的源代码,这仍然是一个选项。

在testing一个类的私有方法时,尤其是在testing驱动的开发中 ,你可能希望在编写任何代码之前devise一个小testing。

通过访问私有成员和方法创build一个testing,可以testing难以专门针对只访问公共方法的代码区域。 如果公共方法涉及多个步骤,则可以由多个私有方法组成,然后可以单独进行testing。

优点:

  • 可以testing更精细的粒度

缺点:

  • testing代码必须与源代码驻留在相同的文件中,这可能会更难以维护
  • 与.class输出文件类似,它们必须保持在源代码中声明的相同包中

然而,如果连续testing需要这种方法,这可能是一个信号,应该提取私有方法,这可以在传统的公共方式中进行testing。

这是一个如何工作的复杂的例子:

 // Import statements and package declarations public class ClassToTest { private int decrement(int toDecrement) { toDecrement--; return toDecrement; } // Constructor and the rest of the class public static class StaticInnerTest extends TestCase { public StaticInnerTest(){ super(); } public void testDecrement(){ int number = 10; ClassToTest toTest= new ClassToTest(); int decremented = toTest.decrement(number); assertEquals(9, decremented); } public static void main(String[] args) { junit.textui.TestRunner.run(StaticInnerTest.class); } } } 

内部类将被编译为ClassToTest$StaticInnerTest

另请参阅: Java技巧106:静态内部类的乐趣和利润

如果您想要testing您不愿意或无法更改的现有代码,则reflection是一个不错的select。

如果这个类的devise仍然是灵活的,并且你有一个复杂的私有方法,你想单独testing,我build议你把它分离出来,并分别testing这个类。 这不必改变原始类的公共接口; 它可以在内部创build一个辅助类的实例并调用辅助方法。

如果你想testing来自助手方法的困难错误条件,你可以进一步。 从助手类中提取一个接口,添加一个公共的getter和setter到原始类来注入助手类(通过它的接口使用),然后将助手类的模拟版本注入到原始类中,以testing原始类回应助手的例外情况。 如果您想testing原始类而不testing助手类,这种方法也是有帮助的。

如果使用Spring, ReflectionTestUtils提供了一些方便的工具,只需要很less的工作就可以帮到您。 例如,build立一个私人成员的模拟,而不会被迫添加一个不需要的公共设置者:

 ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject); 

testing私有方法会破坏类的封装,因为每次更改内部实现时都会破坏客户端代码(在这种情况下是testing)。

所以不要testing私有方法。

如果你想testing那些你不能改变代码的遗留应用程序的私有方法,一个选项是jMockit ,它允许你创build一个对象模拟 ,即使它们对于类是私有的。

既然你在使用JUnit,那么看看junit-addons 。 它有能力忽略Java安全模型并访问私有方法和属性。

在Spring框架中,您可以使用此方法testing私有方法:

 ReflectionTestUtils.invokeMethod() 

例如:

 ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data"); 

JUnit.org常见问题页面的答案:

但是,如果你必须…

如果您使用的是JDK 1.3或更高版本,则可以使用reflection来借助PrivilegedAccessor来颠覆访问控制机制。 有关如何使用它的详细信息,请阅读这篇文章 。

如果您使用的是JDK 1.6或更高版本,并使用@Test注释testing,则可以使用Dp4j在您的testing方法中注入reflection。 有关如何使用它的详细信息,请参阅此testing脚本 。

PS我是Dp4j的主要贡献者,问我是否需要帮助。 🙂

我倾向于不testing私有方法。 有疯狂。 就我个人而言,我相信你只应该testing你公开的接口(包括受保护和内部方法)。

私有方法只能在同一个类中访问。 所以没有办法从任何testing类来testing目标类的“私有”方法。 出路在于,您可以手动执行unit testing,也可以将方法从“私有”更改为“受保护”。

然后一个受保护的方法只能在类定义的同一个包中被访问。 因此,testing目标类的受保护方法意味着我们需要在与目标类相同的包中定义testing类。

如果以上所有不符合您的要求,请使用reflection方式访问私有方法。

我build议你重构一下你的代码。 当你必须开始考虑使用reflection或其他types的东西时,为了testing你的代码,你的代码出了问题。

你提到了不同types的问题。 我们从私人领域开始。 在私人领域的情况下,我会添加一个新的构造函数,并注入字段。 而不是这个:

 public class ClassToTest { private final String first = "first"; private final List<String> second = new ArrayList<>(); ... } 

我会用这个:

 public class ClassToTest { private final String first; private final List<String> second; public ClassToTest() { this("first", new ArrayList<>()); } public ClassToTest(final String first, final List<String> second) { this.first = first; this.second = second; } ... } 

即使使用一些遗留代码,这也不会成为问题。 旧的代码将使用一个空的构造函数,如果你问我,重构的代码将看起来更干净,你将能够在testing中注入必要的值而不会reflection。

现在谈谈私人方法。 根据我个人的经验,当你必须存储一个私人的testing方法时,那么这个方法在这个类中是无关紧要的。 在这种情况下,一个常见的模式就是将其封装在一个接口中,比如Callable ,然后在构造函数中使用该接口(使用多重构造函数)。

 public ClassToTest() { this(...); } public ClassToTest(final Callable<T> privateMethodLogic) { this.privateMethodLogic = privateMethodLogic; } 

大部分我写的看起来像是一个dependency injection模式。 根据我个人的经验,在testing时它是非常有用的,我认为这种代码更简洁,更容易维护。 我会说相同的嵌套类。 如果一个嵌套的类包含繁重的逻辑,那么如果你把它作为一个包私有类来移动,并且将它注入需要它的类,会更好。

在重构和维护遗留代码时,我还使用了其他几种devise模式,但这一切都取决于要testing的代码的情况。 大多数情况下使用reflection并不是一个问题,但是当你有一个经过严格testing的企业应用程序,并且在每次部署之前运行testing,一切都变得非常慢(这只是烦人的,我不喜欢那种东西)。

也有setter注射,但我不会推荐使用它。 我最好坚持一个构造函数,并在真正需要的时候初始化所有的东西,留下注入必要的依赖的可能性。

正如上面提到的那样,一个好的方法是通过你的公共接口对它们进行testing。

如果你这样做,最好使用代码覆盖工具(如Emma)来查看你的私有方法是否实际上是从你的testing中执行的。

今天,我推了一个图书馆来帮助testing私人的方法和领域。 它的devise考虑到了Android,但它确实可以用于任何Java项目。

如果你有私人方法或字段或构造函数的代码,你可以使用BoundBox 。 它正是你正在寻找的。 下面是一个访问Android活动的两个私有字段的testing示例:

 @UiThreadTest public void testCompute() { // Given boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity()); // When boundBoxOfMainActivity.boundBox_getButtonMain().performClick(); // Then assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText()); } 

BoundBox可以很容易地testing私有/受保护的字段,方法和构造函数。 你甚至可以访问被inheritance隐藏的东西。 的确,BoundBox打破封装。 它会给你所有的通过reflection的访问, 一切都在编译时检查。

这是testing一些遗留代码的理想select。 小心使用它。 ;)

https://github.com/stephanenicolas/boundbox

Here is my generic function to test private fields:

 protected <F> F getPrivateField(String fieldName, Object obj) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); return (F)field.get(obj); } 

First, I'll throw this question out: Why do your private members need isolated testing? Are they that complex, providing such complicated behaviors as to require testing apart from the public surface? It's unit testing, not 'line-of-code' testing. Don't sweat the small stuff.

If they are that big, big enough that these private members are each a 'unit' large in complexity — consider refactoring such private members out of this class.

If refactoring is inappropriate or infeasible, can you use the strategy pattern to replace access to these private member functions / member classes when under unit test? Under unit test, the strategy would provide added validation, but in release builds it would be simple passthrough.

I'd use reflection , since I don't like the idea of changing the access to a package on the declared method just for the sake of testing. However, I usually just test the public methods which should also ensure the private methods are working correctly.

you can't use reflection to get private methods from outside the owner class, the private modifier affects reflection also

这不是真的。 You most certainly can, as mentioned in Cem Catikkas's answer .

I recently had this problem and wrote a little tool, called Picklock , that avoids the problems of explicitly using the Java reflection API, two examples:

Calling methods, eg private void method(String s) – by Java reflection

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

Calling methods, eg private void method(String s) – by Picklock

 interface Accessible { void method(String s); } ... Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class); a.method("mystring"); 

Setting fields, eg private BigInteger amount; – by Java reflection

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

Setting fields, eg private BigInteger amount; – by Picklock

 interface Accessible { void setAmount(BigInteger amount); } ... Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class); a.setAmount(BigInteger.valueOf(42)); 

Please see below for an example;

The following import statement should be added:

 import org.powermock.reflect.Whitebox; 

Now you can directly pass the object which has the private method, method name to be called, and additional parameters as below.

 Whitebox.invokeMethod(obj, "privateMethod", "param1");