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


2725

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

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


257
测试私有方法的最佳方法是不直接对其进行测试
Surya 2010年


383
我不同意。长期或难以理解的(公共)方法必须重构。不测试您获得的小型(私有)方法而不是仅测试公共方法是愚蠢的。
Michael Piefel 2011年

181
不仅仅因为可见性是愚蠢的就测试任何方法。甚至单元测试也应该只用最小的代码,如果只测试公共方法,您现在将永远无法确定错误发生的地方-该方法或其他方法。
Dainius

126
您需要测试类功能,而不是其实现。想测试私有方法吗?测试调用它们的公共方法。如果对该类提供的功能进行了全面测试,则表明其内部结构是正确且可靠的;您无需测试内部条件。测试应保持与测试类的解耦。
Calimar41年

Answers:


1639

更新:

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

@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)必须与私人玩耍。


350
如果您可能不知道API,这很有用,但是如果您必须以这种方式测试私有方法,那么您的设计就会有些麻烦。正如另一位发布者所说,单元测试应该测试班级的合同:如果合同范围太广并且实例化了太多的系统,那么应该解决设计问题。
andygavin

21
很有用。使用此功能时,请务必牢记如果在混淆后进行测试会严重失败。
Rick Minerich'2

20
示例代码并没有为我工作,但本作更清楚thigs:java2s.com/Tutorial/Java/0125__Reflection/...
罗布

35
比直接使用反射更好的方法是使用诸如Powermock之类的库。
Michael Piefel 2011年

25
这就是为什么测试驱动设计会有所帮助的原因。它可以帮助您找出需要暴露哪些内容以验证行为。
托尔比约恩Ravn的安徒生

621

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

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

6
此外,我们无论如何都不会接近100%的代码覆盖率,所以为什么不花时间进行质量测试,而这些测试实际上是客户直接使用的方法。
格林奇

30
@grinch点上。通过测试私有方法,您唯一获得的就是调试信息,这就是调试器的作用。如果您对班级合同的测试涵盖了所有内容,那么您将拥有所需的所有信息。私有方法是实现细节。如果对它们进行测试,则即使您的合同没有更改,您也必须在每次实现更改时都更改测试。在软件的整个生命周期中,其成本可能比其带来的收益高得多。
Erik Madsen 2013年

9
@AlexWien你是对的。承保范围不正确。我应该说的是“如果您对班级合同的测试涵盖了所有有意义的输入和所有有意义的状态”。您正确地指出,通过代码的公共接口或间接引用代码可能无法进行测试。如果您正在测试的某些代码是这种情况,我倾向于考虑:(1)合同过于宽松(例如方法中的参数太多),或者(2)合同过于模糊(例如方法行为)随类状态而变化很大)。我会考虑两种设计的味道。
Erik Madsen

16
几乎所有私有方法都不应直接测试(以减少维护成本)。例外:科学方法的形式可能像f(a(b(c(x),d(y))),a(e(x,z)),b(f(x(y,z),z))其中a,b,c,d,e和f是极其复杂的表达式,但在此单个公式之外,这些表达式无用。某些功能(认为MD5)难以反转,因此很难(NP-Hard)选择能够完全覆盖行为空间的参数。独立测试a,b,c,d,e,f更容易。将它们公开或打包私有意味着它们是可重用的。解决方案:将它们设为私有,但对其进行测试。
同名的

4
@Eponymous我对这个有点不满意。在我看来,面向对象的纯粹主义者会说您应该使用一种面向对象的方法,而不是静态的私有方法,从而允许您通过公共实例方法进行测试。但话又说回来,在科学框架中,内存开销完全可能是一个令人担忧的问题,我认为这是静态方法的理由。
Erik Madsen 2013年

323

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

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

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

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


24
问题是如何测试私有方法。您说要为此创建新类(并增加更多的复杂性),然后建议不要创建新类。那么如何测试私有方法呢?
Dainius

27
@Dainius:我不建议创建一个新类以便您可以测试该方法。我确实建议编写测试可以帮助您改进设计:好的设计易于测试。
杰伊·巴祖兹

13
但是您同意良好的OOD将仅公开(公开)该类/对象正常工作所必需的方法吗?所有其他应为private / protectec。因此,在某些私有方法中,会有一些逻辑,而IMO测试这些方法只会提高软件质量。当然,我同意,如果某些代码很复杂,应该将其划分为单独的方法/类。
Dainius 2012年

6
@Danius:添加一个新类,在大多数情况下会降低复杂性。只是测量一下。在某些情况下,可测试性与OOD对抗。现代方法是支持可测试性。
AlexWien

5
正是使用测试反馈来驱动和改进设计的方法!听您的测试。如果它们很难编写,那就告诉您有关代码的重要信息!
pnschofield '16

289

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

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

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


76
为此+1。我认为这是对该问题的最佳答案。通过测试私有方法,您正在测试实现。这违背了单元测试的目的,即测试类合同的输入/输出。测试应对实现有足够的了解,以模拟对其依赖项调用的方法。而已。如果您不必更改测试就无法更改实现,则很可能是您的测试策略不佳。
Colin M

10
@Colin M但这并不是他真正的问题;)让他决定,您不知道该项目。
亚伦·马库斯

2
不是真的 通过使用私有方法的公共方法来测试私有方法的一小部分可能会花费很多精力。在到达调用私有方法的行之前,公共方法可能需要进行大量设置
ACV

1
我同意。您应该测试合同是否履行。您不应该测试如何完成。如果没有达到100%的代码覆盖率(使用私有方法),则实现了联系,则该代码可能无效或无用。
wuppi '19

207

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

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

@JimmyT。,取决于“生产代码”是给谁使用的。我将为VPN内的应用程序(其目标用户是sysadmins)生成的代码称为生产代码。
Pacerier

@Pacerier是什么意思?
Jimmy T.

1
第五种选择,就像上面提到的正在测试调用私有方法的公共方法?正确?
user2441441 2016年

1
@LorenzoLerate我一直在努力解决这个问题,因为我正在进行嵌入式开发,并且我希望我的软件尽可能小。尺寸限制和性能是我能想到的唯一原因。

我真的很喜欢使用反射的测试:在这里,模式Method method = targetClass.getDeclaredMethod(methodName,argClasses); method.setAccessible(true); 返回method.invoke(targetObject,argObjects);
Aguid

127

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


16
这是IMO的最佳答案,或者像通常所说的那样,测试行为,而不是方法。单元测试不能替代源代码指标,静态代码分析工具和代码审查。如果私有方法如此复杂,以至于它们需要单独的测试,则可能需要对其进行重构,而不是对其进行更多的测试。
Dan Haynes

14
因此,不要编写私有方法,而只需创建10个其他小类?
剃刀2015年

1
不,基本上,这意味着您可以使用调用私有方法的公共方法来测试它们的功能。
Akshat Sharda

66

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

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

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

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


5
尽管您永远不要公开一种仅用于测试方法的方法,但这private并不是唯一的选择。没有访问修饰符,package private并且意味着只要单元测试位于同一包中,就可以对其进行单元测试。
ngreen

1
我在评论您的答案,特别是第1点,而不是OP。不必仅仅因为您不希望公开该方法而将其设为私有。
ngreen

1
@ngreen true thx-我对“ public”一词很懒。我已经更新了答案,以包括公共的,受保护的,默认的(并提及反射)。我要说明的重点是,某些代码是秘密的,但是这并不妨碍我们对其进行测试。
Richard Le Mesurier 2014年

2
感谢您为每个人提供真实的例子。多数答案是好的,但从理论上来说。在现实世界中,我们确实需要从合同中隐藏实施,而我们仍然需要对其进行测试。阅读所有这些,我想,也许这应该是测试一个全新的水平,用不同的名称
Z. Khullah

1
另一个示例-Crawler HTML解析器。必须将整个html页面解析为域对象,这需要大量的精巧逻辑来验证结构的小部分。将其分解为方法并从一个公共方法调用它是有意义的,但是让所有较小的方法组成它都是公共的没有意义,每个HTML页面创建10个类也没有多大意义。
下界

59

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


真实的事实。问题是实现更加复杂时,例如一个公共方法调用了几个私有方法。哪一个失败了?或者,如果它是一个复杂的私有方法,不能被暴露,也没有转移到一个新的类
Z. Khullah

您已经提到了为什么应该测试私有方法。您说“那可能是”,所以您不确定。IMO,是否应该测试一种方法与其访问级别正交。
魏秋

39

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

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

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


1
我不知道这一点,这很有趣,我仍然认为,如果您需要这种注释,则会遇到设计问题。
勒布

2
对我来说,这就像是在说,不要给您的消防管理员一个一次性的钥匙来测试消防演习,而是让您的前门处于解锁状态,并在门上写上一个标语:“已解锁以进行测试-如果您不是来自我们的公司,请不要输入”。
杰里米·克里(Jeremy Krieg)神父

@FrJeremyKrieg-这是什么意思?
MasterJoe2

1
@ MasterJoe2:防火测试的目的是提高建筑物的安全性,以免发生火灾。但是您必须让管理员访问您的建筑物。如果您始终将前门保持解锁状态,则会增加未经授权进入的风险,并降低其安全性。VisibleForTesting模式也是如此-您正在增加未经授权访问的风险,以减少“起火”的风险。最好仅在需要时(例如,使用反射)而不是将其永久解锁(非私有),才将访问权限一次性授予进行测试。
杰里米·克里格神父

34

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

我将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);

2
确保下载整个JUnit插件(sourceforge.net/projects/junit-addons)软件包,而不是Source Forge推荐项目PrivateAccessor。
skia.heliou 2015年

2
并且确保Class在这些方法中将a 用作第一个参数时,仅访问static成员。
skia.heliou 2015年

31

Spring Framework中,您可以使用以下方法测试私有方法:

ReflectionTestUtils.invokeMethod()

例如:

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

到目前为止,最干净,最简洁的解决方案,但前提是您使用的是Spring。
Denis Nikolaenko

28

使用 Cem Catikkas的Java 反射解决方案尝试过之后,我不得不说他的解决方案比我在此描述的更为优雅。但是,如果您正在寻找使用反射的替代方法,并且可以访问要测试的源,则仍然可以选择。

测试类的私有方法可能有好处,尤其是在测试驱动的开发中,您希望在编写任何代码之前先设计小型测试。

创建具有对私有成员和方法的访问权的测试可以测试仅通过访问公共方法而难以专门针对的代码区域。如果公共方法涉及多个步骤,则它可以包含多个私有方法,然后可以分别进行测试。

优点:

  • 可以测试到更细的粒度

缺点:

  • 测试代码必须与源代码位于同一文件中,这可能更难以维护
  • 与.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:获取乐趣和收益的静态内部类


26

正如其他人所说的...不要直接测试私有方法。这里有一些想法:

  1. 保持所有方法的小型化和重点化(易于测试,易于发现问题所在)
  2. 使用代码覆盖率工具。我喜欢Cobertura(开心的一天,看起来好像有新版本发布了!)

在单元测试上运行代码覆盖率。如果您发现方法未经过完全测试,请添加到测试中以扩大覆盖范围。争取100%的代码覆盖率,但要意识到您可能不会明白。


进行代码覆盖。无论私有方法中包含哪种逻辑,您仍在通过公共方法调用这些逻辑。代码覆盖率工具可以向您显示测试覆盖了哪些部分,因此您可以查看您的私有方法是否经过测试。
coolcfan 2012年

我看到了唯一的公共方法是main []的类,它们弹出GUI并连接数据库和世界各地的两个Web服务器。容易说“不要间接测试” ...
Audrius Meskauskas 2014年

26

私有方法由公共方法使用。否则,它们就是无效代码。这就是为什么要测试公共方法,声明公共方法的预期结果以及由此使用的私有方法的原因。

在私有方法上运行单元测试之前,应通过调试来测试私有方法。

也可以使用测试驱动的开发来调试它们,调试单元测试,直到满足所有声明为止。

我个人认为最好使用TDD创建类。创建公共方法存根,然后使用预先定义的所有断言生成单元测试,因此在对方法进行编码之前,应确定该方法的预期结果。这样,您就不会沿着使单元测试断言适合结果的错误道路走下去。这样,您的课程将变得很健壮,并且在所有单元测试都通过后就可以满足要求。


2
尽管这是事实,但它可能导致某些非常复杂的测试-这是一次更好地测试单个方法(单元测试)而不是整个方法的更好模式。这不是一个可怕的建议,但我认为这里有更好的答案-这应该是不得已的方法。
Bill K


24

如果您要测试您不愿或无法更改的现有代码,则反射是一个不错的选择。

如果该类的设计仍然很灵活,并且您想单独测试一个复杂的私有方法,则建议您将其拉到一个单独的类中并分别测试该类。不必更改原始类的公共接口。它可以在内部创建helper类的实例并调用helper方法。

如果要测试来自辅助方法的困难错误条件,则可以更进一步。从帮助程序类中提取一个接口,向原始类中添加一个公共获取器和设置器,以注入辅助程序类(通过其接口使用),然后将模拟的辅助程序类注入到原始类中,以测试原始类的效果响应助手的异常。如果您想测试原始类而不测试助手类,则此方法也很有用。


19

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

因此,请勿测试私有方法。


4
单元测试和src代码是一对。如果更改src,也许您必须更改单元测试。这就是junit测试的意义。他们应保证一切照旧进行。如果它们更改了代码,那么如果它们中断就可以了。
AlexWien

1
如果代码更改,则不同意单元测试应更改。如果命令您在不更改类的现有功能的情况下向其添加功能,则即使更改了代码,现有的单元测试也应该通过。正如Peter所提到的,单元测试应该测试接口,而不是内部的接口。在“测试驱动开发”中,在编写代码之前先创建单元测试,以专注于类的接口,而不是内部如何解决。
Harald Coppoolse 2015年

16

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

但是如果你必须...

如果使用的是JDK 1.3或更高版本,则可以在PrivilegedAccessor的帮助下使用反射来破坏访问控制机制。有关如何使用它的详细信息,请阅读本文

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

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


16

如果要在无法更改代码的情况下测试旧版应用程序的私有方法,则Java的一个选项是jMockit,即使您的对象是类的私有对象,它也可以创建对象的对象。


4
作为jmockit库的一部分,您可以访问Deencapsulation类,该类使测试私有方法变得容易:Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Domenic D.

我一直在使用这种方法。非常有用
MedicineMan

14

我倾向于不测试私有方法。有疯狂。我个人认为,您应该只测试公开的接口(包括受保护的方法和内部方法)。


14

如果您使用的是JUnit,请查看junit-addons。它具有忽略Java安全模型并访问私有方法和属性的能力。


13

我建议您重构一下代码。当您不得不开始考虑使用反射或其他类型的东西时,仅仅为了测试您的代码,您的代码就出了问题。

您提到了不同类型的问题。让我们从私有字段开始。如果是私有字段,我会添加一个新的构造函数并将字段注入其中。代替这个:

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;
    }
    ...
}

即使有一些遗留代码,这也不是问题。旧代码将使用一个空的构造函数,如果您问我,重构后的代码看起来会更干净,并且可以在测试中注入必要的值而无需进行反思。

现在介绍私有方法。以我的个人经验,当您必须使用私有方法进行测试时,则该方法与该类无关。在这种情况下,一种常见的模式是将其包装在一个接口中,例如Callable然后在构造器中传递该接口(具有多个构造器技巧):

public ClassToTest() {
    this(...);
}

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

我写的大部分内容看起来都是依赖注入模式。以我的个人经验,它在测试时确实很有用,而且我认为这种代码更简洁并且更易于维护。对于嵌套类,我也会说同样的话。如果嵌套类包含繁琐的逻辑,则最好将其作为包私有类移动并将其注入需要它的类中。

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

也有二传手注射,但我不建议使用它。我最好坚持使用构造函数,并在确实需要时初始化所有内容,从而留出注入必要依赖的可能性。


1
不赞成ClassToTest(Callable)注射。这变得ClassToTest更加复杂。把事情简单化。而且,这还需要其他人告诉应该成为老板的ClassToTest事情ClassToTest。有一个注入逻辑的地方,但事实并非如此。您使课程变得更加难以维护,而不是更加简单。
Loduwijk

同样,如果您的测试方法X不会将X复杂性增加到可能引起更多问题的地步,因此也需要进行额外的测试,则是更可取的。 (这不是无限循环;每次迭代都可能小于之前的迭代,但仍然很烦人)
Loduwijk

2
您能解释一下为什么这会使班级ClassToTest难于维护吗?实际上,它使您的应用程序更易于维护,您建议为所需的每个不同值first和“第二”变量创建新类吗?
GROX13

1
测试方法不会增加复杂性。只是您的班级写得不好,以至于无法测试。
GROX13年

难以维护,因为该类更加复杂。class A{ A(){} f(){} }class A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }。更简单。如果那不是真的,并且确实很容易维护提供类的逻辑作为构造函数参数,那么我们将所有逻辑作为构造函数参数传递。我只是看不到您如何声称class B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }曾经使A易于维护。在某些情况下,这样的注入是有道理的,但我不认为您的示例“更易于维护”
Loduwijk

12

私有方法只能在同一类中访问。因此,无法从任何测试类中测试目标类的“私有”方法。解决方法是您可以手动执行单元测试,也可以将方法从“专用”更改为“受保护”。

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

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


您正在将“受保护”与“友好”混合在一起。受保护的方法只能由其对象可分配给目标类(即子类)的类访问。
Sayo Oladeji

1
实际上,在Java中没有“友好的”,术语是“包”,并且缺少私有/公共/受保护的修饰符来表明这一点。我会更正这个答案,但是已经有一个非常好的答案了,所以我只建议删除它。
Bill K

我几乎投反对票,一直想着“这个答案是完全错误的。” 直到我到达最后一句话为止,这与其余答案相矛盾。最后一句话应该是第一句话。
Loduwijk

11

正如以上许多建议所建议的,一种好方法是通过您的公共接口对其进行测试。

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


您不应该间接测试!不仅通过覆盖接触; 测试是否达到预期结果!
AlexWien

11

这是我测试私有字段的通用函数:

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);
}

10

请参见下面的示例;

应该添加以下导入语句:

import org.powermock.reflect.Whitebox;

现在,您可以直接传递具有私有方法,要调用的方法名称以及其他参数的对象,如下所示。

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

10

今天,我推出了一个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


1
刚刚尝试过,BoundBox是一个简单而优雅的解决方案!
2013年

9

首先,我将提出这个问题:为什么您的私人成员需要隔离测试?它们是否如此复杂,提供的行为如此复杂,以至于需要进行公开测试以外的测试?它是单元测试,而不是“代码行”测试。不要汗流stuff背。

如果它们足够大,足够大,以至于这些私人成员各自都是复杂的“单位”,请考虑将此类私人成员从此类中重构出来。

如果重构不合适或不可行,在进行单元测试时,是否可以使用策略模式替换对这些私有成员函数/成员类的访问?在单元测试中,该策略将提供更多的验证,但是在发行版本中,这将是简单的传递。


3
因为通常将公共方法中的特定代码重构为内部私有方法,这实际上是您可能出错的关键逻辑部分。您想独立于公共方法进行测试
oxbow_lakes

即使是最短的代码,有时甚至没有单元测试也是不正确的。只需尝试计算2个地理角度之间的差即可。4行代码,大多数在第一次尝试时不会正确执行。此类方法需要进行单元测试,因为它是可信任代码的基础。(这样的有用代码也可以是公开的;受保护程度较小
AlexWien

8

我最近遇到了这个问题,并编写了一个名为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));

7

PowerMockito是为此而制造的。使用Maven依赖

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-mockito-release-full</artifactId>
    <version>1.6.4</version>
</dependency>

那你可以做

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.