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

如何使用JUnit测试具有内部私有方法、字段或嵌套类的类?

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

1163116 次浏览

测试私有方法的最佳方法是通过另一个公共方法。如果无法做到这一点,则以下条件之一为真:

  1. 私有方法是死代码
  2. 你正在测试的课程附近有一种设计气味
  3. 您尝试测试的方法不应该是私有的

一般来说,单元测试旨在实现类或单元的公共接口。因此,私有方法是您不希望显式测试的实现细节。

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

从这篇文章:使用JUnit和SuiteRunner测试私有方法(比尔·维纳斯),你基本上有4个选择:

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

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

Java我会使用反射,因为我不喜欢仅仅为了测试而改变对声明方法上包的访问的想法。然而,我通常只测试公共方法,这也应该确保私有方法正常工作。

您不能使用反射从所有者类外部获取私有方法,私有修饰符也会影响反射

这不是真的。你当然可以,正如在Cem Catikkas的回答中提到的。

如果您有一些遗留的Java应用程序,并且您不允许更改方法的可见性,那么测试私有方法的最佳方法是使用反射

在内部,我们使用帮助程序来获取/设置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)需要与私人玩耍。

在尝试了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:静态内部类的乐趣和利润

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

如果类的设计仍然是灵活的,并且你有一个复杂的私有方法,你想单独测试,我建议你把它拉到一个单独的类中,单独测试那个类。这不必改变原始类的公共接口;它可以在内部创建一个帮助类的实例并调用帮助方法。

如果你想测试来自helper方法的困难错误条件,你可以更进一步。从helper类中提取一个接口,在原始类中添加一个公共getter和setter以注入helper类(通过其接口使用),然后将helper类的模拟版本注入原始类以测试原始类如何响应helper的异常。如果你想测试原始类而不同时测试helper类,这种方法也很有帮助。

如果您想测试无法更改代码的遗留应用程序的私有方法,Java的一个选项是jMockit,它允许您创建对象的模拟,即使它们对类是私有的。

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

我解决这些问题的通常方法是梳理出一个包含有趣位的新类。通常,这个方法和它与之交互的字段,也许可以将另一个或两个方法提取到一个新类中。

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

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

正如上面许多人所建议的,一个好方法是通过您的公共接口测试它们。

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

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

首先,我会抛出这个问题:为什么你的私有成员需要隔离测试?它们有那么复杂吗?提供如此复杂的行为以至于需要在公共表面之外进行测试?这是单元测试,不是“代码行”测试。不要为小事担心。

如果它们足够大,以至于这些私有成员都是一个复杂的“单元”——考虑将这些私有成员重构出这个类。

如果重构不合适或不可行,你可以在单元测试下使用策略模式来替换对这些私有成员函数/成员类的访问吗?在单元测试下,该策略将提供额外的验证,但在发布版本中,它将是简单的直通。

测试私有方法会破坏类的封装,因为每次更改内部实现都会破坏客户端代码(在本例中为测试)。

所以不要测试私有方法。

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

  1. 保持所有方法小而集中(易于测试,易于发现错误)
  2. 使用代码覆盖工具。我喜欢Cobertura(哦,快乐的一天,看起来新版本出来了!)

在单元测试上运行代码覆盖率。如果你发现方法没有完全测试,请添加到测试中以提高覆盖率。以100%的代码覆盖率为目标,但要意识到你可能不会得到它。

您可以关闭Java访问限制进行反射,这样私有就毫无意义了。

setAccessible(true)调用就是这样做的。

唯一的限制是ClassLoader可能不允许您这样做。

参见颠覆单元测试Java访问保护(Ross Burton),了解在Java中执行此操作的方法。

在C#中,你可以使用System.Reflection,但在Java我不知道。如果你“觉得你需要单元测试私有方法”,我的猜测是还有其他问题…

我会认真考虑用新的眼光重新审视我的建筑…

如果您的测试类与应该测试的类在同一个包中怎么办?

但是在不同的目录中,srcclasses用于源代码,test/srctest/classes用于测试类。让classestest/classes在您的类路径中。

过去我用反射来做Java,在我看来这是一个很大的错误。

严格来说,你应该没有编写直接测试私有方法的单元测试。你应该要测试的是类与其他对象的公共契约;你永远不应该直接测试对象的内部。如果另一个开发人员想对类进行一个小的内部更改,而不影响类的公共契约,那么他/她必须修改你的基于反射的测试以确保它能工作。如果你在整个项目中反复这样做,单元测试就不再是衡量代码健康状况的有用指标,并开始成为开发的障碍和开发团队的烦恼。

相反,我建议使用代码覆盖工具,例如Cobertura,以确保您编写的单元测试对私有方法中的代码提供了不错的覆盖。这样,您就间接地测试了私有方法正在做什么,并保持了更高级别的敏捷性。

仅举两个例子说明我想在哪里测试私有方法:

  1. 解密例程-我不会想让任何人都能看到它们为了测试,其他人都可以用它们来解密。但它们是代码固有的,复杂的,并且需要始终工作(明显的例外是反射,在大多数情况下,当SecurityManager没有配置为防止这种情况时,它甚至可以用于查看私有方法)。

  2. 创建SDK社区消费。在这里,公众承担了完全不同的意义,因为这是全世界都能看到的代码(不仅仅是我的应用程序内部)。我把将代码转换为私有方法,如果我不这样做希望SDK用户看到它-我别把这看成是代码味关于SDK编程的工作原理。但是当然我还需要测试我的私人方法,他们在哪里我的SDK的功能实际上生命

我理解只测试“契约”的想法。但我认为没有人可以主张实际上不测试代码-因人而异。

因此,我的权衡涉及使用反射使JUnit测试复杂化,而不是损害我的安全性和SDK。

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

我使用junitx.util.私有访问器-包来Java,它有很多有用的单行程序来访问私有方法和私有字段。

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

答案来自JUnit.orgFAQ页面

但如果你一定要…

如果您使用的是JDK 1.3或更高版本,您可以使用反射来颠覆借助权限获取者的权限改造机制。有关如何使用它的详细信息,请阅读这篇文章

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

附注:我是Dp4j的主要贡献者。如果您需要帮助,请询问。:)

我只测试公共接口,但众所周知,我使特定的私有方法受到保护,所以我可以完全模拟它们,或者添加特定于单元测试目的的额外步骤。一般情况下,我可以从单元测试中设置挂钩标志,以使某些方法故意导致异常,以便能够测试故障路径;异常触发代码仅在受保护方法的重写实现中的测试路径中。

不过,我尽量减少了这方面的需要,并且我总是记录确切的原因以避免混淆。

我不确定这是否是一种好的技术,但我开发了以下模式来单元测试私有方法:

我不会修改我要测试的方法的可见性并添加一个额外的方法。相反,我为我要测试的每个私有方法添加了一个额外的公共方法。我将此附加方法称为Test-Port并用前缀t_表示它们。这个Test-Port方法然后简单地访问相应的私有方法。

此外,我向Test-Port方法添加了一个布尔标志,以决定是否从外部通过Test-Port方法授予对私有方法的访问权限。然后,该标志在我放置的静态类中全局设置,例如应用程序的其他全局设置。所以我可以在一个地方打开和关闭对私有方法的访问,例如,在相应的单元测试中。

Groovy有一个bug /feature,您可以通过它调用私有方法,就好像它们是公共的一样。因此,如果您能够在项目中使用Groovy,它是一个可以代替反射的选项。查看此页面的示例。

JML有一个spec_public注释注释语法,允许您在测试期间将方法指定为公共:

private /*@ spec_public @*/ int methodName(){...}

这种语法在2.4隐私修饰符和可见性中讨论。还有一个将JML规范转换为JUnit测试的程序。我不确定它的工作效果如何或它的功能是什么,但似乎没有必要,因为JML本身就是一个可行的测试框架。

今天,我推出了一个Java库来帮助测试私有方法和字段。它的设计考虑到了Android,但它确实可以用于任何Java项目。

如果你有一些带有私有方法、字段或构造函数的代码,你可以使用装订盒。它完全符合你的要求。下面是一个测试示例,它访问Android活动的两个私有字段来测试它:

@UiThreadTestpublic void testCompute() {
// GivenboundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());
// WhenboundBoxOfMainActivity.boundBox_getButtonMain().performClick();
// ThenassertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());}

边界框可以轻松测试私有/受保护的字段、方法和构造函数。您甚至可以访问通过继承隐藏的东西。事实上,BoundBox打破了封装。它将让您通过反射访问所有这些,<强>但一切都在编译时检查。

它是测试一些遗留代码的理想选择。小心使用它。;)

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

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

它们也可以使用测试驱动开发进行调试,调试单元测试,直到满足所有断言。

我个人认为最好使用TDD创建类;创建公共方法存根,然后使用所有预先定义的断言生成单元测试,因此方法的预期结果在编码之前就确定了。这样,你就不会走上让单元测试断言符合结果的错误道路。当你所有的单元测试都通过时,你的类就会变得健壮并满足要求。

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

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

私有方法只能在同一个类中访问。所以没有办法从任何测试类测试目标类的“私有”方法。出路是你可以手动执行单元测试,或者可以将方法从“私有”更改为“保护”。

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

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

如果使用Spring,反射测试工具提供了一些方便的工具,可以以最小的努力在这里提供帮助。例如,在私有成员上设置模拟,而不会被迫添加不需要的公共setter:

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

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

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

ReflectionTestUtils.invokeMethod()

例如:

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

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

这将告诉任何使用此方法的人要小心,即使在包中也不要直接访问它。此外,测试类不必在同一个包身体中,而是在测试文件夹下的同一个包中。

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

请看下面的例子。

应添加以下导入语句:

import org.powermock.reflect.Whitebox;

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

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

快速添加到Cem Catikka的回答,当使用期望异常:

请记住,您预期的异常将被包装在InvocationTargetException中,因此为了到达您的异常,您必须抛出您收到的InvocationTargetException的原因。类似于(在BizService上测试私有方法validateRequest()):

@Rulepublic ExpectedException thrown = ExpectedException.none();
@Autowired(required = true)private BizService svc;

@Testpublic 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());}}

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

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

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

我写的大部分代码看起来都是依赖注入模式。根据我的个人经验,它在测试时非常有用,而且我认为这种代码更干净,更容易维护。嵌套类也是如此。如果嵌套类包含重逻辑,最好将其作为包私有类移动并将其注入需要它的类中。

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

还有setter注入,但我不建议使用它。我最好坚持使用构造函数,并在真正必要时初始化所有内容,留下注入必要依赖项的可能性。

您可以使用PowerMockito为要测试的私有方法中调用/使用的私有字段和私有方法设置返回值:

例如,为私有方法设置返回值:

MyClient classUnderTest = PowerMockito.spy(new MyClient());
// Set the expected return valuePowerMockito.doReturn(20).when(classUnderTest, "myPrivateMethod", anyString(), anyInt());// This is very important. Otherwise, it will not workclassUnderTest.myPrivateMethod();
// Setting the private field value as someValue:Whitebox.setInternalState(classUnderTest, "privateField", someValue);

最后,您可以通过以下方式验证测试中的私有方法是否根据上面的设置值返回正确的值:

String msg = Whitebox.invokeMethod(obj, "privateMethodToBeTested", "param1");Assert.assertEquals(privateMsg, msg);

如果class UnderTest私有方法没有返回值,但它设置了另一个私有字段,那么您可以获取该私有字段值以查看它是否设置正确:

// To get the value of a private fieldMyClass obj = Whitebox.getInternalState(classUnderTest, "foo");assertThat(obj, is(notNull(MyClass.class))); // Or test value

如果您担心没有像许多帖子建议的那样测试私有方法,请考虑代码覆盖率工具将准确确定测试了多少代码以及泄漏的位置,因此可以量化地接受这样做。

将问题的作者引向“变通办法”的答案对社区造成了巨大的伤害。测试是所有工程学科的重要组成部分。你不会想买一辆没有经过适当测试的汽车,并且以有意义的方式进行了测试,那么为什么会有人想买或使用测试不佳的软件呢?人们这样做的原因可能是因为测试不佳的软件的影响在事实发生后很久就会感觉到,我们通常不会将它们与身体伤害联系起来。

这是一个非常危险的观念,很难改变,但我们有责任提供安全的产品,无论管理层强迫我们做什么。

我们必须努力营造一个鼓励优秀软件工程实践的环境。这并不意味着排斥我们中那些不把自己的手艺当回事的弱者/懒惰者,而是要创造一种问责和自我反省的现状,鼓励每个人追求成长,无论是精神上还是技能上。

我还在学习,自己可能有错误的看法/观点,但我坚信,我们需要对良好的做法负责,避免不负责任的黑客攻击或解决问题的方法。

从测试框架测试Java私有方法的最佳和适当的合法方法是对方法进行@刘志强注释,因此相同的方法将像公共方法一样对测试框架可见。

C++中:在包含具有要测试的私有函数的类标头之前。

使用此代码:

#define private public#define protected public

PowerMockito就是为此而生的。

使用Maven依赖项:

    <dependency><groupId>org.powermock</groupId><artifactId>powermock-core</artifactId><version>2.0.7</version><scope>test</scope></dependency>

然后你可以做

import org.powermock.reflect.Whitebox;...MyClass sut = new MyClass();SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);

我和我的团队正在使用Typemock,它有一个允许您假非公开方式的API。

最近他们添加了假不可见类型和使用xUnit的功能。

如果你真的需要直接测试私有方法/类等…,你可以使用反射,就像在其他答案中已经提到的那样。然而,如果涉及到这一点,与其直接处理反射,我宁愿使用框架提供的实用程序类。例如,对于Java我们有:

至于如何使用它们,你可以在网上找到很多文章。这是我特别喜欢的一篇:

您可以创建一个特殊的公共方法来代理要测试的私有方法。使用IntelliJ时,@TestOnly注释是开箱即用的。缺点是如果有人想在公共上下文中使用私有方法,他可以这样做。但他会被注释和方法名称警告。在IntelliJ上,这样做时会出现警告。

import org.jetbrains.annotations.TestOnly
class MyClass {
private void aPrivateMethod() {}
@TestOnlypublic void aPrivateMethodForTest() {aPrivateMethod()}}

还有另一种方法可以测试您的私有方法。如果您在运行配置中“启用断言”,那么您可以在方法本身内部单元测试您的方法。例如;

assert ("Ercan".equals(person1.name));assert (Person.count == 2);

PowerMock.Whitebox是我见过的最好的选择,但是当我阅读它的源代码时,它读取带有反射的私有字段,所以我想我有我的答案:

  • 使用PowerMock测试私有内部状态(字段),或者只是反射,而无需引入另一个独立性的开销
  • 对于私有方法:实际上,这个问题本身的赞成票,以及大量的评论和答案,表明这是一个非常并发和有争议的话题没有明确的答案可以给出适合每种情况。我明白只应该测试契约,但我们也有覆盖范围要考虑。实际上,我怀疑只有测试合约才能100%使类对错误免疫。私有方法是那些在定义它的类中处理数据的方法,因此其他类不感兴趣,所以我们不能简单地暴露以使其可测试。我会尽量不要考验他们,但是当你必须的时候,去做吧,忘掉这里所有的答案。你比互联网上任何其他人都更清楚你的处境和限制。当你控制了你的代码,使用它。考虑但不要过度思考。

一段时间后,当我重新考虑它时,我仍然相信这是真的,但我看到了更好的方法。

首先,Powermock.Whitebox仍然可用。

而且,MockitoWhitebox在v2之后被隐藏了(我能找到的Whitebox的最新版本是testImplementation 'org.mockito:mockito-core:1.10.19'),它一直是org.mockito.internal包的一部分,将来很容易发生重大变化(见这篇文章)。所以现在我倾向于不使用它。

在Gradle/Maven项目中,如果你定义了私有方法或字段,没有其他方法可以通过反射来访问它们,所以第一部分保持不变。但是,如果你将可见性更改为“包私有”,在#0包中遵循相同结构的测试将可以访问它们。这也是鼓励我们在maintest包中创建相同层次结构的另一个重要原因。因此,当您可以控制生产代码和测试时,删除private访问修饰符可能是您的最佳选择,因为相对而言它不会造成巨大的影响。并且,这使得测试和私人方法间谍成为可能。

@Autowiredprivate SomeService service; // with a package private method "doSomething()"
@Testvoid shouldReturnTrueDoSomething() {assertThat(doSomething(input), is(true)); // package private method testing}
@Testvoid shouldReturnTrueWhenServiceThrowsException() {SomeService spy = Mockito.spy(service); // spying real objectdoThrow(new AppException()).when(spy).doSomething(input); // spy package private method...
}

当涉及到内部字段时,在Spring中你有ReflectionUtils.setField()

最后,有时我们可以绕过问题本身:如果有覆盖要求需要满足,也许你可以将这些私有方法移动到一个内部静态类中,并在Jacoco中忽略这个类。

Android有android.support.annotation包的#0注释。

@VisibleForTesting注释表示带注释的方法比使方法可测试所需的通常更可见。此注释有一个可选的otherwise参数,允许您指定如果不需要使其可见以进行测试,方法的可见性应该是什么。Lint使用otherwise参数来强制实现预期的可见性。

在实践中,这意味着您应该打开一个方法进行测试,@VisibleForTesting注释将显示一个警告。

例如

package com.mypackage;
public class ClassA {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)static void myMethod() {
}}

当您在同一个包(com.mypackage)中调用ClassA.my方法()时,您将看到警告。

在此处输入图片描述

C++(自C++11起)将测试类添加为朋友可以完美地工作,并且不会破坏生产封装。

假设我们有一些类Foo,其中一些私有函数确实需要测试,还有一些类FooTest应该可以访问Foo的私有成员。然后我们应该写以下内容:

// prod.h: some production code header
// forward declaration is enough// we should not include testing headers into production codeclass FooTest;
class Foo{// that does not affect Foo's functionality// but now we have access to Foo's members from FooTestfriend FooTest;public:Foo();private:bool veryComplicatedPrivateFuncThatReallyRequiresTesting();}
// test.cpp: some test#include <prod.h>
class FooTest{public:void complicatedFisture() {Foo foo;ASSERT_TRUE(foo.veryComplicatedPrivateFuncThatReallyRequiresTesting());}}
int main(int /*argc*/, char* argv[]){FooTest test;test.complicatedFixture();  // and it really works!}

下面的反射测试用例可以是泛指来测试原子性的私有方法

import com.google.common.base.Preconditions;
import org.springframework.test.util.ReflectionTestUtils;
/*** <p>* Invoker* </p>** @author* @created Oct-10-2019*/public class Invoker {private Object target;private String methodName;private Object[] arguments;
public <T> T invoke() {try {Preconditions.checkNotNull(target, "Target cannot be empty");Preconditions.checkNotNull(methodName, "MethodName cannot be empty");if (null == arguments) {return ReflectionTestUtils.invokeMethod(target, methodName);} else {return ReflectionTestUtils.invokeMethod(target, methodName, arguments);}} catch (Exception e) {throw e;}}
public Invoker withTarget(Object target) {this.target = target;return this;}
public Invoker withMethod(String methodName) {this.methodName = methodName;return this;}
public Invoker withArguments(Object... args) {this.arguments = args;return this;}
}
Object privateMethodResponse = new Invoker().withTarget(targetObject).withMethod(PRIVATE_METHOD_NAME_TO_INVOKE).withArguments(arg1, arg2, arg3).invoke();Assert.assertNotNutll(privateMethodResponse)

如果您只使用莫基托

测试公有方法时,可以确保覆盖公有方法中的所有情况。

假设你是一个仅限Mockito的用户(你不被允许或不想使用PowerMock或反射或任何此类工具),并且你不想更改正在测试的现有代码或库,这可能是最好的方法。

如果您选择这种方式,您唯一需要处理的是在私有方法中本地声明的变量(用户定义的对象)。如果私有方法依赖于本地声明的变量对象及其方法,请确保您将这些用户定义的对象全局声明为私有对象而不是本地声明的对象。您可以在本地实例化这些对象。

这允许您对这些对象进行模拟并将它们注入测试对象。您还可以模拟(使用时/然后)他们的方法。

这将允许您在测试公共方法时测试私有方法而不会出错。

优势

  1. 代码覆盖率
  2. 能够测试完整的私有方法。

缺点

  1. 对象的范围-如果您不希望对象暴露给同一类中的其他方法,这可能不是您的方式。
  2. 当在不同的公共方法和/或同一方法中多次调用时,您可能最终会多次测试私有方法。

嘿,如果您在弹簧上,请使用此实用程序类。

ReflectionTestUtils.invokeMethod(new ClassName(), "privateMethodName");

在你的课堂上:

namespace my_namespace {#ifdef UNIT_TESTclass test_class;#endif
class my_class {public:#ifdef UNIT_TESTfriend class test_class;#endifprivate:void fun() { cout << "I am private" << endl; }}}

在单元测试中:

#ifndef UNIT_TEST#define UNIT_TEST#endif
#include "my_class.h"
class my_namespace::test_class {public:void fun() { my_obj.fun(); }private:my_class my_obj;}
void my_unit_test() {test_class test_obj;test_obj.fun(); // here you accessed the private function ;)}

这是我的龙目岛样本:

public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {Student student = new Student();
Field privateFieldName = Student.class.getDeclaredField("name");privateFieldName.setAccessible(true);privateFieldName.set(student, "Naruto");
Field privateFieldAge = Student.class.getDeclaredField("age");privateFieldAge.setAccessible(true);privateFieldAge.set(student, "28");
System.out.println(student.toString());
Method privateMethodGetInfo = Student.class.getDeclaredMethod("getInfo", String.class, String.class);privateMethodGetInfo.setAccessible(true);System.out.println(privateMethodGetInfo.invoke(student, "Sasuke", "29"));}

@Setter@Getter@ToStringclass Student {private String name;private String age;    
private String getInfo(String name, String age) {return name + "-" + age;}}

我认为大多数答案都太粗糙了。是否应该测试私有方法取决于您的代码。

我过去使用私有方法测试,我通过反思来做到这一点。它对我有效。我意识到它的问题,但对我来说,这是最好的解决方案。

我有一个繁重的应用程序,它模拟了100多万人的人类行为。每个人都由一个对象表示。该应用程序的主要目的是跟踪某些东西(疾病、信息、思想)如何在人群中传播。

为此,我有一些方法可以将疾病或信息从一个对象传递到另一个对象。绝对没有理由将这些方法公之于众,因为最终用户对人与人之间的一次传递不感兴趣。最终用户只对它如何在人群中传播的大局感兴趣。所以这些方法是私人的。

但是我想确定的是,将一点信息从一个人传递给另一个人这一单一行为是否发挥了应有的作用。通过公开的用户界面进行测试也是不可能的,因为它还没有公开,而且我觉得仅仅为了测试的目的公开它是很尴尬的。应用程序的最终输出是由始终执行的数亿个这样的单一步骤定义的。我也不能测试最终输出,因为这涉及复杂的库存稳定性,无法测试。

因此,我测试应用程序的方法是通过测试将信息从一个人传递给另一个人的单个步骤。这些都是私有方法。所以我使用反射来测试它。

我讲这个故事是为了表明这不是简单的黑白故事。这取决于您的应用程序什么是最好的。在某些情况下,通过反射测试私有方法可能是您的最佳选择。

如果这里的一些人在我的用例中知道更好的解决方案,我当然很乐意接受纠正。

我想分享一个关于测试的规则,特别是与这个主题相关的规则:

我认为你永远不应该调整生产代码,以便更容易写测试。

其他帖子中有一些建议说你应该调整原始类以测试私有方法-请先红色此警告。

如果我们将方法/字段的可访问性更改为打包私有或受保护,只是为了让测试可以访问它,那么我们就违背了私有访问指令存在的目的。

当我们想要测试驱动开发时,为什么我们应该有私有字段/方法/类?我们是否应该将所有内容声明为包私有,甚至是公共的,这样我们就可以不费吹灰之力地进行测试?-我不这么认为。

从另一个角度来看:测试不应影响生产应用程序的性能和执行。

如果我们只是为了更容易测试而更改生产代码,这可能会以某种方式给应用程序的性能和执行带来负担。

如果有人开始将私有访问更改为包私有,那么开发人员最终可能会想出其他“巧妙的想法”,向原始类添加更多代码。这会给易读性带来额外的噪音,并会影响应用程序的性能。

随着将私有访问权限更改为一些限制较少的权限,我们为开发人员在未来的应用程序开发中滥用新情况打开了可能性。我们没有强制他/她以正确的方式开发,而是用新的可能性诱惑他/她,并赋予他在未来做出错误选择的能力。

当然,这个规则可能会有一些例外,但要清楚地理解,什么是规则,什么是例外?我们需要绝对确定我们知道为什么会引入这种例外。

在我看来,改变方法的访问修饰符只是为了能够运行测试是一个坏主意。在我们公司,我们对此进行了很多讨论,在我看来,测试私有方法的真正好方法是使用Java反射或其他使该方法可测试的框架。我为复杂的私有方法做了多次这样做,这有助于我保持测试小、可读和可维护。

在我阅读了这里的所有答案之后,我只是不同意那些说“如果你需要测试私有方法,那么就会有代码气味”甚至“不要测试私有方法”的人……所以我给你举一个小例子:

假设我有一个类,其中包含一个公共方法和几个私有方法:

public class ConwaysGameOfLife {
private boolean[][] generationData = new boolean[128][128];
/*** Compute the next generation and return the new state* Also saving the new state in generationData*/public boolean[][] computeNextGeneration() {boolean[][] tempData = new boolean[128][128];
for (int yPos=0; yPos<=generationData.length; yPos++) {for (int xPos=0; xPos<=generationData[yPos].length; xPos++) {int neighbors = countNeighbors(yPos, xPos);tempData[yPos][xPos] = determineCellState(neighbors, yPos, xPos);}}
generationData = tempData;return generationData;}
/*** Counting the neighbors for a cell on given position considering all the edge cases** @return the amount of found neighbors for a cell*/private int countNeighbors(int yPos, int xPos) {}
/*** Determine the cell state depending on the amount of neighbors of a cell and on a current state of the cell** @return the new cell state*/private boolean determineCellState(int neighborsAmount, int yPos, int xPos) {}}

所以至少对于“countNeighbors”方法,我需要测试八个边缘情况和一些一般情况(直接位于角落的单元格、直接位于矩阵边缘的单元格和中间的单元格)。所以如果我只是试图通过“computeNextGener”方法覆盖所有情况,并且重构后,一些测试是红色的,识别bug的位置可能很耗时。

如果我在重构和优化之后分别测试“确定的细胞状态”和“CountNeighbors”,“computeNextgen”和“确定的细胞状态”的测试是红色的,我很确定错误将出现在“确定的细胞状态”方法中。

此外,如果你从一开始就为这些方法编写单元测试,这些测试将帮助你开发方法/算法,而不需要考虑和包装公共方法中的其他方法调用和case。你可以在方法中编写快速的小测试来覆盖你的case……例如,如果名为countNeighbors_should_return_right_amount_of_neighbors_for_the_right_top_corner_cell()的测试失败了,那么在哪里寻找bug就很清楚了。