如何对具有内部私有方法,字段或嵌套类的类进行单元测试(使用xUnit)?或者通过内部链接(static
在C/C++中)或在私有(匿名)命名空间中使其成为私有的函数?
为了能够运行测试而更改方法或函数的访问修饰符似乎很糟糕.
如果您有一些遗留Java应用程序,并且不允许更改方法的可见性,那么测试私有方法的最佳方法是使用反射.
在内部,我们使用帮助器来获取/设置@Jailbreak
和private
变量以及调用private static
和private
方法.以下模式将允许您执行与私有方法和字段相关的任何操作.当然,你private static
不能通过反射来改变变量.
@Jailbreak Foo foo = new Foo(); // Direct, *type-safe* access to *all* foo's members foo.privateMethod(x, y, z); foo.privateField = value;
对于领域:
Method method = TargetClass.getDeclaredMethod(methodName, argClasses); method.setAccessible(true); return method.invoke(targetObject, argObjects);
注意:
1.private static final
让你看看TargetClass.getDeclaredMethod(methodName, argClasses)
方法.同样的事情也适用private
.
2.getDeclaredField
需要与私人一起玩耍.
测试私有方法的最佳方法是通过另一种公共方法.如果无法执行此操作,则满足下列条件之一:
私有方法是死代码
您正在测试的课程附近有一种设计气味
您尝试测试的方法不应该是私有的
当我在一个足够复杂的类中使用私有方法时,我觉得需要直接测试私有方法,这就是代码味道:我的类太复杂了.
我解决此类问题的常用方法是梳理出一个包含有趣内容的新类.通常,此方法及其与之交互的字段以及可能的另一种方法可以被提取到新类中.
新类将这些方法公开为"公共",因此它们可以进行单元测试.新旧课程现在都比原来的课程简单,这对我来说很棒(我需要保持简单,否则我会迷路!).
请注意,我并不是说人们在不使用大脑的情况下创建课程!这里的要点是使用单元测试的力量来帮助您找到好的新类.
我过去曾使用反射来为Java做这个,在我看来这是一个很大的错误.
严格来说,您不应该编写直接测试私有方法的单元测试.您应该测试的是该类与其他对象的公共合同; 你永远不应该直接测试对象的内部.如果另一个开发人员想要对该类进行少量内部更改(这不会影响类公共合同),则他/她必须修改基于反射的测试以确保其有效.如果在整个项目中反复执行此操作,则单元测试将不再是对代码运行状况的有用度量,并开始成为开发的障碍,并对开发团队造成烦扰.
我建议改为使用Cobertura等代码覆盖工具,以确保您编写的单元测试在私有方法中提供良好的代码覆盖率.这样,您可以间接测试私有方法正在做什么,并保持更高的敏捷性.
从本文:使用JUnit和SuiteRunner(Bill Venners)测试私有方法,您基本上有4个选项:
不要测试私有方法.
给方法包访问权限.
使用嵌套测试类.
使用反射.
通常,单元测试旨在运用类或单元的公共接口.因此,私有方法是您不希望显式测试的实现细节.
只是我想要测试私有方法的两个例子:
解密例程 - 我不想让任何人看到它只是为了测试,否则任何人都可以使用它们来解密.但它们是代码固有的,复杂的,并且需要始终工作(明显的例外是反射,在大多数情况下,甚至可以用来查看私有方法,当SecurityManager
没有配置来防止这种情况时).
创建用于社区消费的SDK.在这里公共具有完全不同的含义,因为这是整个世界可能看到的代码(不仅仅是我的应用程序的内部代码).如果我不希望SDK用户看到它,我会将代码放入私有方法中 - 我不认为这是代码味道,仅仅是SDK编程的工作方式.但是我当然还需要测试我的私有方法,它们实际上是SDK的功能所在.
我理解只测试"合同"的想法.但是我没有看到人们可以提倡实际上没有测试代码 - 你的里程可能会有所不同.
因此,我的权衡涉及使JUnits与反射复杂化,而不是损害我的安全性和SDK.
私有方法由公共方法调用,因此公共方法的输入还应测试由这些公共方法调用的私有方法.当公共方法失败时,那可能是私有方法失败.
我使用的另一种方法是将私有方法更改为包私有或受保护,然后使用Google Guava库的@VisibleForTesting注释对其进行补充.
这将告诉使用此方法的任何人要小心,即使在包中也不能直接访问它.另外,测试类不需要在物理上相同的包中,而是在测试文件夹下的相同包中.
例如,如果要测试的方法是,src/main/java/mypackage/MyClass.java
那么应该放入测试调用src/test/java/mypackage/MyClassTest.java
.这样,您就可以访问测试类中的测试方法.
为了测试大和古怪类遗留代码,它往往是能够测试一个私人(或公共)方法我正在写非常有帮助的现在.
我使用junitx.util.PrivateAccessor -package for Java.许多有用的单行程序用于访问私有方法和私有字段.
import junitx.util.PrivateAccessor; PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue); PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);
在尝试了使用 Java 反射的 Cem Catikkas 解决方案之后,我不得不说他的解决方案比我在这里描述的更优雅.但是,如果您正在寻找使用反射的替代方法,并且可以访问您正在测试的源,那么这仍然是一个选项.
测试类的私有方法可能是有用的,特别是在测试驱动开发中,您希望在编写任何代码之前设计小型测试.
创建可访问私有成员和方法的测试可以测试难以专门定位的代码区域,只能访问公共方法.如果公共方法涉及多个步骤,则它可以包含多个私有方法,然后可以单独进行测试.
好处:
可以测试更精细的粒度
缺点:
测试代码必须与源代码位于同一文件中,这可能更难以维护
与.class输出文件类似,它们必须保持在源代码中声明的相同包中
但是,如果连续测试需要这种方法,则可能是应该提取私有方法的信号,可以以传统的公共方式进行测试.
这是一个如何工作的复杂的例子:
// 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:用于娱乐和利润的静态内部类
在Spring Framework中,您可以使用此方法测试私有方法:
ReflectionTestUtils.invokeMethod()
例如:
ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");
正如其他人所说......不要直接测试私人方法.以下是一些想法:
保持所有方法小而集中(易于测试,易于发现错误)
使用代码覆盖工具.我喜欢Cobertura(哦,快乐的一天,看起来像是一个新版本!)
在单元测试上运行代码覆盖率.如果您发现方法未经过全面测试,请添加测试以获得覆盖率.瞄准100%的代码覆盖率,但意识到你可能不会得到它.
私有方法由公共方法使用.否则,它们就是死代码.这就是为什么你测试公共方法,断言公共方法的预期结果,从而断言它消耗的私有方法.
在对公共方法运行单元测试之前,应通过调试来测试私有方法的测试.
它们也可以使用测试驱动开发进行调试,调试单元测试直到满足所有断言.
我个人认为使用TDD创建类更好; 创建公共方法存根,然后使用预先定义的所有断言生成单元测试,因此在编码之前确定方法的预期结果.这样,您就不会走错路径,使单元测试断言符合结果.您的课程非常强大,并且在所有单元测试通过后都符合要求.
如果使用Spring,ReflectionTestUtils提供了一些方便的工具,只需要很少的工作就可以帮助你.例如,要在私有成员上设置模拟而不必强制添加不需要的公共setter:
ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);
如果您正在尝试测试您不愿意或无法更改的现有代码,那么反射是一个不错的选择.
如果类的设计仍然是灵活的,并且你有一个复杂的私有方法,你想单独测试,我建议你把它拉出一个单独的类并分别测试该类.这不必更改原始类的公共接口; 它可以在内部创建辅助类的实例并调用辅助方法.
如果您想测试来自辅助方法的困难错误条件,您可以更进一步.从helper类中提取一个接口,将一个公共getter和setter添加到原始类中以注入帮助器类(通过其接口使用),然后将一个helper类的模拟版本注入到原始类中以测试原始类的方式响应助手的异常.如果您想在不测试帮助程序类的情况下测试原始类,这种方法也很有用.
测试私有方法会破坏类的封装,因为每次更改内部实现时都会破坏客户端代码(在本例中为测试).
所以不要测试私有方法.
如果您想测试遗留应用程序的私有方法,而您无法更改代码,那么Java的一个选项是jMockit,它允许您为对象创建模拟,即使它们是该类的私有对象.
JUnit.org FAQ页面的答案:
但如果你必须......
如果您使用的是JDK 1.3或更高版本,则可以使用反射在PrivilegedAccessor的帮助下破坏访问控制机制.有关如何使用它的详细信息,请阅读本文.
如果您使用的是JDK 1.6或更高版本并使用@Test注释测试,则可以使用Dp4j在测试方法中注入反射.有关如何使用它的详细信息,请参阅此测试脚本.
PS我是Dp4j的主要贡献者,问我是否需要帮助.:)
我倾向于不测试私有方法.有疯狂.就个人而言,我相信你应该只测试你公开的接口(包括受保护的和内部的方法).
如果您正在使用JUnit,请查看junit-addons.它能够忽略Java安全模型并访问私有方法和属性.
我建议你稍微重构一下你的代码.当你不得不开始考虑使用反射或其他类型的东西时,为了测试你的代码,你的代码就会出现问题.
你提到了不同类型的问题.让我们从私人领域开始.在私有字段的情况下,我会添加一个新的构造函数并将注入的字段注入其中.而不是这个:
public class ClassToTest { private final String first = "first"; private final Listsecond = new ArrayList<>(); ... }
我用过这个:
public class ClassToTest { private final String first; private final Listsecond; public ClassToTest() { this("first", new ArrayList<>()); } public ClassToTest(final String first, final List second) { this.first = first; this.second = second; } ... }
即使使用一些遗留代码,这也不会成为问题.旧代码将使用空构造函数,如果您问我,重构代码看起来更干净,并且您将能够在没有反射的情况下在测试中注入必要的值.
现在关于私人方法.根据我个人的经验,当您必须存根私有方法进行测试时,该方法与该类无关.在这种情况下,一个常见的模式是将它包装在一个接口中,Callable
然后你也在构造函数中传入该接口(使用那个多个构造函数技巧):
public ClassToTest() { this(...); } public ClassToTest(final CallableprivateMethodLogic) { this.privateMethodLogic = privateMethodLogic; }
我写的大部分内容看起来都是依赖注入模式.根据我的个人经验,它在测试时非常有用,我认为这种代码更清晰,更易于维护.关于嵌套类,我会说同样的.如果嵌套类包含重逻辑,那么如果将它作为包私有类移动并将其注入需要它的类中会更好.
我在重构和维护遗留代码时也使用了其他几种设计模式,但这完全取决于要测试的代码的情况.使用反射主要不是问题,但是当你有一个经过严格测试的企业应用程序并且在每次部署之前运行测试时,一切都变得非常慢(这很烦人,而且我不喜欢那种东西).
还有二次注射,但我不建议使用它.我最好坚持使用构造函数并在必要时初始化所有内容,留下注入必要依赖项的可能性.
私有方法只能在同一个类中访问.因此,无法从任何测试类测试目标类的"私有"方法.一种方法是您可以手动执行单元测试,也可以将方法从"私有"更改为"受保护".
然后,只能在定义类的同一个包中访问受保护的方法.因此,测试目标类的受保护方法意味着我们需要在与目标类相同的包中定义测试类.
如果以上所有内容都不符合您的要求,请使用反射方式访问私有方法.
正如上面提到的那样,一个好方法是通过公共接口测试它们.
如果你这样做,最好使用代码覆盖工具(如Emma)来查看你的私有方法是否实际上是从你的测试中执行的.
这是我测试私有字段的通用函数:
protectedF getPrivateField(String fieldName, Object obj) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); return (F)field.get(obj); }
今天,我推出了一个Java库来帮助测试私有方法和字段.它在设计时考虑到了Android,但它确实可以用于任何Java项目.
如果您获得了一些私有方法或字段或构造函数的代码,则可以使用BoundBox.它完全符合您的要求.下面是一个测试示例,该测试访问Android活动的两个私有字段以对其进行测试:
@UiThreadTest public void testCompute() { // Given boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity()); // When boundBoxOfMainActivity.boundBox_getButtonMain().performClick(); // Then assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText()); }
BoundBox可以轻松测试私有/受保护的字段,方法和构造函数.您甚至可以访问通过继承隐藏的内容.实际上,BoundBox打破了封装.它将允许您通过反射访问所有这些,但是在编译时检查所有内容.
它是测试一些遗留代码的理想选择.仔细使用它.;)
https://github.com/stephanenicolas/boundbox
请看下面的例子;
应添加以下import语句:
import org.powermock.reflect.Whitebox;
现在,您可以直接传递具有私有方法,要调用的方法名称以及其他参数的对象,如下所示.
Whitebox.invokeMethod(obj, "privateMethod", "param1");
首先,我会抛出这个问题:为什么你的私人成员需要隔离测试?它们是否复杂,提供如此复杂的行为以至于需要在公共场所进行测试?它是单元测试,而不是"代码行"测试.不要出汗小东西.
如果他们那么大,足够大,以至于这些私人成员都是一个复杂性很大的"单位" - 考虑重构这些私人成员.
如果重构不合适或不可行,您可以使用策略模式在单元测试下替换对这些私有成员函数/成员类的访问吗?在单元测试下,该策略将提供额外的验证,但在发布版本中,它将是简单的直通.
我最近遇到了这个问题并编写了一个名为Picklock的小工具,它避免了显式使用Java反射API的问题,两个例子:
调用方法,例如private void method(String s)
- 通过Java反射
Method method = targetClass.getDeclaredMethod("method", String.class); method.setAccessible(true); return method.invoke(targetObject, "mystring");
调用方法,例如private void method(String s)
- 由Picklock
interface Accessible { void method(String s); } ... Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class); a.method("mystring");
设置字段,例如private BigInteger amount;
- 通过Java反射
Field field = targetClass.getDeclaredField("amount"); field.setAccessible(true); field.set(object, BigInteger.valueOf(42));
设置字段,例如private BigInteger amount;
- 由Picklock
interface Accessible { void setAmount(BigInteger amount); } ... Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class); a.setAmount(BigInteger.valueOf(42));
对于Java我会使用反射,因为我不喜欢只是为了测试而在声明的方法上更改对包的访问的想法.但是,我通常只测试公共方法,这也应该确保私有方法正常工作.
您不能使用反射从所有者类外部获取私有方法,私有修饰符也会影响反射
这不是真的.你肯定可以,正如Cem Catikkas的回答中提到的那样.
在c ++中:在包含具有要对其进行测试的私有函数的类头之前
使用此代码:
#define private public #define protected public
PowerMockito就是为此而制造的.使用maven依赖
org.powermock powermock-mockito-release-full 1.6.4
那你可以做
import org.powermock.reflect.Whitebox; ... MyClass sut = new MyClass(); SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);