How do I test a private function or a class that has private methods, fields or inner classes?
如何对具有内部私有方法、字段或嵌套类的类进行单元测试(使用XUnit)?还是通过内部链接(C/C++中的EDOCX1 0)或私有(匿名)命名空间私有化的函数?
为了能够运行测试而更改方法或函数的访问修饰符似乎很糟糕。
Update:
Some 10 years later perhaps the best way to test a private method, or any inaccessible member, is via
@Jailbreak from the Manifold framework.
1
2
3
4 @Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;This way your code remains type-safe and readable. No design compromises, no overexposing methods and fields for the sake of tests.
如果您有一些遗留Java应用程序,并且不允许更改方法的可见性,则测试私有方法的最佳方法是使用反射。
在内部,我们使用助手来获取/设置
1 2 3 | Method method = TargetClass.getDeclaredMethod(methodName, argClasses); method.setAccessible(true); return method.invoke(targetObject, argObjects); |
田地:
1 2 3 | Field field = TargetClass.getDeclaredField(fieldName); field.setAccessible(true); field.set(object, value); |
Notes:
1.TargetClass.getDeclaredMethod(methodName, argClasses) lets you look intoprivate methods. The same thing applies for
getDeclaredField .
2. ThesetAccessible(true) is required to play around with privates.
测试私有方法的最佳方法是通过另一个公共方法。如果不能做到这一点,则以下条件之一为真:
当我在一个足够复杂的类中拥有私有方法时,我觉得需要直接测试私有方法,这是一种代码味道:我的类太复杂了。
我处理这些问题的通常方法是梳理出一个包含有趣部分的新类。通常,这个方法和它与之交互的字段,可能还有一两个方法可以提取到一个新类中。
新类将这些方法公开为"public",因此可以对它们进行单元测试。新的和旧的类现在都比原来的类简单,这对我很好(我需要保持简单,否则我会迷路!).
注意,我不是建议人们不用大脑来创建类!这里的重点是使用单元测试的力量来帮助您找到好的新类。
过去我用反射来做Java,我认为这是一个很大的错误。
严格来说,您不应该编写直接测试私有方法的单元测试。您应该测试的是类与其他对象之间的公共契约;您不应该直接测试对象的内部。如果另一个开发人员想要对类进行一个小的内部更改,这不会影响类公共契约,那么他/她必须修改您的基于反射的测试,以确保它可以工作。如果您在整个项目中重复这样做,那么单元测试就不再是代码健康状况的一个有用的度量,并且开始成为开发的一个障碍,成为开发团队的一个烦恼。
我建议您改为使用代码覆盖工具(如cobertura),以确保您编写的单元测试能够在私有方法中提供代码的适当覆盖。这样,您就可以间接地测试私有方法正在做什么,并保持更高的敏捷性级别。
从本文中:使用JUnit和Suiterner(Bill Venners)测试私有方法,您基本上有4个选项:
- Don't test private methods.
- Give the methods package access.
- Use a nested test class.
- Use reflection.
一般来说,单元测试是为了执行类或单元的公共接口。因此,私有方法是您不希望显式测试的实现细节。
我想测试私有方法的两个示例:
我理解只测试"合同"的想法。但我不认为有人可以主张实际上不测试代码——您的里程可能会有所不同。
因此,我的权衡涉及到用反射使JUnit复杂化,而不是损害我的安全性和SDK。
私有方法由公共方法调用,因此对公共方法的输入也应该测试那些公共方法调用的私有方法。当公共方法失败时,这可能是私有方法中的失败。
我使用的另一种方法是将private方法更改为package private或protected,然后用google guava库的@visiblefortesting注释对其进行补充。
这将告诉使用此方法的任何人要小心,即使在包中也不能直接访问它。另外,测试类在物理上不需要在同一个包中,而是在测试文件夹下的同一个包中。
例如,如果要测试的方法在
要用大型且古怪的类测试遗留代码,能够测试我现在正在编写的一个私有(或公共)方法通常非常有用。
我使用Junix.UTI.PrimeAccess(Java)包。访问私有方法和私有字段的许多有用的一行程序。
1 2 3 4 | import junitx.util.PrivateAccessor; PrivateAccessor.setField(myObjectReference,"myCrucialButHardToReachPrivateField", myNewValue); PrivateAccessor.invoke(myObjectReference,"privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args); |
我已经尝试过使用Cyjkas的Java反射解决方案,我不得不说他是一个比我在这里描述的更优雅的解决方案。但是,如果您正在寻找使用反射的替代方法,并且能够访问正在测试的源,那么这仍然是一个选项。
在测试类的私有方法方面,尤其是在测试驱动的开发中,可能有一些优点,您希望在编写任何代码之前设计小的测试。
通过访问私有成员和方法创建一个测试,可以测试代码区域,这些区域很难通过只访问公共方法来明确目标。如果一个公共方法涉及到几个步骤,它可以由几个私有方法组成,然后可以单独测试这些方法。
优势:
- 可以测试到更细的粒度
缺点:
- 测试代码必须位于同一个文件作为源代码,可以更难维护
- 与.class输出文件类似,它们必须保留在源代码中声明的同一个包中。
但是,如果连续测试需要这种方法,则可能是提取私有方法的信号,这种方法可以用传统的公共方法进行测试。
下面是一个复杂的例子,说明了这是如何工作的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // 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); } } } |
内部类将编译为
参见:Java技巧106:静态内部类,用于娱乐和利润
正如其他人所说…不要直接测试私有方法。以下是一些想法:
在单元测试上运行代码覆盖率。如果您看到方法没有被完全测试,请添加到测试中以提高覆盖率。目标是100%的代码覆盖率,但要意识到你可能不会得到它。
私有方法被公共方法所消耗。否则,它们就是死代码。这就是为什么要测试公共方法,断言公共方法的预期结果,从而断言它使用的私有方法。
在对公共方法运行单元测试之前,应该通过调试测试私有方法。
它们也可以使用测试驱动的开发进行调试,调试单元测试,直到满足所有断言为止。
我个人认为,最好使用TDD创建类;创建公共方法存根,然后使用预先定义的所有断言生成单元测试,这样在编写代码之前就可以确定方法的预期结果。这样,您就不会走上使单元测试断言适合结果的错误道路。然后,当所有单元测试通过时,您的类是健壮的,并且满足需求。
如果使用Spring,ReflectionTestUtils提供了一些方便的工具,可以用最少的努力来帮助解决这个问题。例如,在不强制添加不需要的公共设置者的情况下,对私有成员设置模拟:
1 | ReflectionTestUtils.setField(theClass,"theUnsettableField", theMockObject); |
如果您试图测试您不愿意或无法更改的现有代码,那么反射是一个不错的选择。
如果类的设计仍然是灵活的,并且您有一个复杂的私有方法要单独测试,那么我建议您将它拉到一个单独的类中,并单独测试该类。这不需要更改原始类的公共接口;它可以在内部创建helper类的实例并调用helper方法。
如果您想测试来自helper方法的困难错误条件,您可以更进一步。从助手类中提取接口,向原始类添加公共getter和setter以插入助手类(通过其接口使用),然后将助手类的模拟版本插入原始类,以测试原始类如何响应助手的异常。如果您想测试原始类而不同时测试助手类,这种方法也很有用。
在Spring框架中,可以使用以下方法测试私有方法:
1 | ReflectionTestUtils.invokeMethod() |
例如:
1 | ReflectionTestUtils.invokeMethod(TestClazz,"createTest","input data"); |
测试私有方法会破坏类的封装,因为每次更改内部实现时都会破坏客户端代码(在本例中是测试)。
所以不要测试私有方法。
来自junit.org常见问题页面的答案:
But if you must...
If you are using JDK 1.3 or higher, you can use reflection to subvert
the access control mechanism with the aid of the PrivilegedAccessor.
For details on how to use it, read this article.If you are using JDK 1.6 or higher and you annotate your tests with
@Test, you can use Dp4j to inject reflection in your test methods. For
details on how to use it, see this test script.
另外,我是DP4J的主要贡献者,问我你是否需要帮助。:)
如果您想测试遗留应用程序的私有方法,在那里您不能更改代码,Java的一个选项是JMOCKIT,它允许您对对象创建模拟,即使它们对类是私有的。
我倾向于不测试私人方法。这里有疯狂。我个人认为,您应该只测试公开的接口(包括受保护的和内部方法)。
如果你用的是JUnit,看看JUnit插件。它有能力忽略Java安全模型并访问私有方法和属性。
私有方法只能在同一类中访问。因此,无法从任何测试类测试目标类的"私有"方法。一种解决方法是,您可以手动执行单元测试,或者将方法从"private"更改为"protected"。
然后,只能在定义类的同一个包中访问受保护的方法。因此,测试目标类的受保护方法意味着我们需要在与目标类相同的包中定义测试类。
如果上述所有内容都不符合您的要求,请使用反射方式访问私有方法。
我建议您稍微重构一下代码。当您不得不开始考虑使用反射或其他类型的东西时,为了测试您的代码,您的代码出现了问题。
你提到了不同类型的问题。让我们从私有领域开始。对于私有字段,我会添加一个新的构造函数并将字段注入其中。而不是这个:
1 2 3 4 5 6 | public class ClassToTest { private final String first ="first"; private final List<String> second = new ArrayList<>(); ... } |
我会用这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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; } ... } |
即使对于某些遗留代码,这也不会是问题。旧代码将使用一个空的构造函数,如果您问我,重构的代码将看起来更干净,并且您将能够在测试中无反射地注入必要的值。
关于私有方法。在我个人的经验中,当你必须截取一个私有的测试方法时,这个方法在这个类中没有任何作用。在这种情况下,一个常见的模式是将它包装在一个接口中,比如
1 2 3 4 5 6 7 | public ClassToTest() { this(...); } public ClassToTest(final Callable<T> privateMethodLogic) { this.privateMethodLogic = privateMethodLogic; } |
我写的大部分看起来像是依赖注入模式。根据我的个人经验,它在测试时非常有用,我认为这种代码更干净,维护起来也更容易。关于嵌套类,我也会这么说。如果嵌套类包含大量逻辑,那么最好将其作为包私有类移动,并将其注入需要它的类中。
在重构和维护遗留代码时,我还使用了其他几种设计模式,但这都取决于要测试的代码的情况。大部分情况下使用反射并不是问题,但是当您有一个经过严格测试的企业应用程序,并且在每次部署之前运行测试时,一切都会变得非常缓慢(这很烦人,我不喜欢这样的东西)。
还有setter注入,但我不建议使用它。我最好坚持使用一个构造函数,并在真正需要的时候初始化所有内容,这样就可以插入必要的依赖项。
正如上面提到的,一个好方法是通过公共接口测试它们。
如果这样做,最好使用代码覆盖工具(如emma)来查看您的私有方法是否实际上是从测试中执行的。
下面是测试私有字段的通用函数:
1 2 3 4 5 6 7 8 | 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); } |
今天,我推了一个Java库来帮助测试私有方法和字段。它是用Android设计的,但它确实可以用于任何Java项目。
如果您有一些带有私有方法、字段或构造函数的代码,则可以使用BoundBox。它完全符合你的要求。下面是访问Android活动的两个私有字段进行测试的测试示例:
1 2 3 4 5 6 7 8 9 10 11 12 | @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
请参阅下面的示例;
应添加以下导入语句:
1 | import org.powermock.reflect.Whitebox; |
现在,您可以直接传递具有私有方法、要调用的方法名和其他参数的对象,如下所示。
1 | Whitebox.invokeMethod(obj,"privateMethod","param1"); |
我最近遇到了这个问题,并编写了一个小工具,名为PcCHILK,它避免了显式使用Java反射API的问题,有两个例子:
调用方法,例如通过Java反射EDCOX1 0
1 2 3 | Method method = targetClass.getDeclaredMethod("method", String.class); method.setAccessible(true); return method.invoke(targetObject,"mystring"); |
通过picklock调用方法,例如
1 2 3 4 5 6 7 | interface Accessible { void method(String s); } ... Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class); a.method("mystring"); |
通过Java反射设置字段,例如EDCOX1×2
1 2 3 | Field field = targetClass.getDeclaredField("amount"); field.setAccessible(true); field.set(object, BigInteger.valueOf(42)); |
通过选择锁设置字段,例如
1 2 3 4 5 6 7 | interface Accessible { void setAmount(BigInteger amount); } ... Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class); a.setAmount(BigInteger.valueOf(42)); |
首先,我将把这个问题抛到一边:为什么您的私有成员需要独立的测试?它们是否如此复杂,提供了如此复杂的行为,以至于需要在公共表面之外进行测试?这是单元测试,而不是"代码行"测试。不要把这些小东西都出汗。
如果它们足够大,以至于这些私有成员都是一个复杂的"单元",那么可以考虑将这些私有成员从这个类中重构出来。
如果重构不合适或不可行,在单元测试时,您可以使用策略模式替换对这些私有成员函数/成员类的访问吗?在单元测试下,该策略将提供额外的验证,但在发布版本中,它将是简单的传递。
对于Java,我会使用反射,因为我不喜欢改变对声明的方法的包的访问只是为了测试。但是,我通常只测试公共方法,这也应该确保私有方法正常工作。
you can't use reflection to get private methods from outside the owner class, the private modifier affects reflection also
这不是真的。正如Cem Catikkas的回答中提到的,你当然可以。
PowerMockito就是为此而设计的。使用Maven依赖项
1 2 3 4 5 | <dependency> <groupId>org.powermock</groupId> powermock-mockito-release-full</artifactId> <version>1.6.4</version> </dependency> |
然后你可以做
1 2 3 4 | import org.powermock.reflect.Whitebox; ... MyClass sut = new MyClass(); SomeType rval = Whitebox.invokeMethod(sut,"myPrivateMethod", params, moreParams); |
在C++中:在包含要测试的私有函数的类头之前
使用此代码:
1 2 | #define private public #define protected public |
您可以关闭Java访问限制以进行反射,这样私有就意味着什么。
唯一的限制是类加载器可能不允许您这样做。
请参见Java测试访问中的Java访问保护(Ross Burton)。
我不确定这是否是一种好的技术,但我开发了以下模式到单元测试私有方法:
我不修改要测试的方法的可见性,也不添加其他方法。相反,我要为我想要测试的每个私有方法添加一个额外的公共方法。我称这个附加方法为
另外,我在
在使用ExpectedException时,可以快速添加@cem catikka的注释:
请记住,预期的异常将被包装在invocationTargetException中,因此为了得到异常,您必须抛出接收到的invocationTargetException的原因。类似(在BizService上测试私有方法validateRequest()):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | @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()); } } |
我想说的是,你们中的一些人担心没有像许多帖子建议的那样测试私有方法,考虑到代码覆盖工具将精确地确定测试了多少代码以及代码在哪里泄漏,所以这样做是可以量化的接受的。
我要说一些需要说但很多人可能不喜欢听的话。你们中那些贡献答案的人,将问题的作者引向了"周围工作",这对社区造成了巨大的伤害。测试是所有工程学科的一个重要组成部分,你不想买一辆没有经过适当测试,并以有意义的方式进行测试的汽车,那么为什么会有人想要购买或使用测试不好的软件呢?不管怎样,人们这样做的原因可能是因为测试不好的软件的影响是事后感觉到的,而且我们通常不会将它们与身体伤害联系起来。这是一种非常危险的观念,很难改变,但我们有责任提供安全的产品,无论管理层如何欺负我们。想想Equifax黑客…
我们必须努力营造一个鼓励良好的软件工程实践的环境,这并不意味着排斥那些不认真对待他们的技术的弱者/懒惰者,而是要创造一种责任感和自我反省的现状,鼓励每个人在精神上和技巧上追求增长。我仍在学习,可能自己有错误的看法/观点,但我坚信,我们需要对良好做法负责,避免不负责任的黑客或解决问题的方法。
在C语言中,你可以使用EDCOX1,1,但是在Java中我不知道。尽管我还是很想回答这个问题,因为如果你"觉得你需要对私有方法进行单元测试",我的猜测是还有其他一些错误…
我会认真考虑用新的眼光重新审视我的建筑…
如果您的测试类与应该测试的类在同一个包中呢?
当然,在另一个目录中,对于您的源代码,
测试框架中测试Java私有方法的最佳合法方法是通过该方法的@ VisualBurfTest注释,因此对于测试框架来说,同样的方法与公共方法一样是可见的。
JML有一个特殊的公共注释注释语法,允许您在测试期间将方法指定为公共的:
1 2 3 | private /*@ spec_public @*/ int methodName(){ ... } |
此语法在http://www.eecs.ucf.edu/~leavens/jml/jmlrefman/jmlrefman_2.html sec12中讨论。还存在一个将JML规范转换为JUnit测试的程序。我不确定它的工作有多好,或者它的功能是什么,但是它似乎没有必要,因为JML本身就是一个可行的测试框架。
可以使用powermockito为要测试的私有方法中调用/使用的私有字段和私有方法设置返回值:
例如,设置私有方法的返回值:
1 2 3 4 5 6 7 8 9 | MyClient classUnderTest = PowerMockito.spy(new MyClient()); //Set expected return value PowerMockito.doReturn(20).when(classUnderTest,"myPrivateMethod", anyString(), anyInt()); //This is very important otherwise it will not work classUnderTest.myPrivateMethod(); //Setting private field value as someValue: Whitebox.setInternalState(classUnderTest,"privateField", someValue); |
最后,您可以通过以下方式验证您的私有方法,即根据上面的设置值返回正确的值:
1 2 | String msg = Whitebox.invokeMethod(obj,"privateMethodToBeTested","param1"); Assert.assertEquals(privateMsg, msg); |
或
如果classundest private方法不返回值,但它设置了另一个private字段,则可以获取该private字段值,以查看其设置是否正确:
1 2 3 | //To get value of private field MyClass obj = Whitebox.getInternalState(classUnderTest,"foo"); assertThat(obj, is(notNull(MyClass.class))); // or test value |
我和我的团队使用的是typemock,它有一个允许您伪造非公共方法的API。最近他们增加了伪造不可见类型和使用XUnit的能力。
还有另一种方法可以测试您的私有方法。如果在运行配置中"启用断言",那么可以在方法内部对方法进行单元测试。例如;
1 2 | assert ("Ercan".equals(person1.name)); assert (Person.count == 2); |
groovy有一个bug/特性,通过这个特性,您可以像调用公共方法一样调用私有方法。因此,如果您能够在项目中使用groovy,那么您可以使用它来代替反射。查看此页面以获取示例。
如果您真的需要测试私有方法/类等。直接地,您可以使用其他答案中已经提到的反射。但是,如果涉及到这一点,我宁愿使用框架提供的Util类,而不是直接处理反射。例如,对于Java,我们有:
ReflectionUtils弹簧框架
反射实用程序JUnit
根据如何使用它们,你可以在网上找到很多文章。这里我特别喜欢:
- Oracle SpReFraskWork.UTI.RealCuffUTLS的Java代码示例
您可以创建一个特殊的公共方法来代理要测试的私有方法。使用intellij时,@testOnly注释不可用。缺点是,如果有人想在公共上下文中使用私有方法,他可以这样做。但是注释和方法名会警告他。在Intellij上,执行此操作时将显示警告。
1 2 3 4 5 6 7 8 9 10 11 | import org.jetbrains.annotations.TestOnly class MyClass { private void aPrivateMethod() {} @TestOnly public void aPrivateMethodForTest() { aPrivateMethod() } } |
我只测试公共接口,但已知我会使特定的私有方法受到保护,这样我就可以完全模拟它们,或者为单元测试目的添加其他特定步骤。一般情况下,挂接我可以从单元测试中设置的标志,以使某些方法有意导致异常能够测试故障路径;异常触发代码仅位于受保护方法的重写实现中的测试路径中。
不过,我尽量减少对此的需要,并且总是记录下避免混淆的确切原因。