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

时间:2008-08-29 16:11:10

标签: unit-testing tdd

如何对具有内部私有方法,字段或嵌套类的类进行单元测试(使用xUnit)?或者通过在{C / C ++中使用internal linkagestatic)或在私有(anonymous)命名空间中使其成为私有的函数?

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

57 个答案:

答案 0 :(得分:1530)

  

<强>更新

     

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

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

答案 1 :(得分:561)

测试私有方法的最佳方法是通过另一种公共方法。如果无法做到这一点,则满足下列条件之一:

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

答案 2 :(得分:288)

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

我解决此类问题的常用方法是梳理出一个包含有趣内容的新类。通常,此方法及其与之交互的字段以及可能的另一种方法可以被提取到新类中。

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

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

答案 3 :(得分:257)

我过去曾使用reflection为Java做这个,而且我认为这是一个很大的错误。

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

我建议使用代码覆盖工具(如Cobertura)来确保您编写的单元测试在私有方法中提供良好的代码覆盖率。这样,您可以间接测试私有方法正在做什么,并保持更高的敏捷性。

答案 4 :(得分:198)

从这篇文章:Testing Private Methods with JUnit and SuiteRunner(Bill Venners),你基本上有4个选择:

  
      
  • 不要测试私有方法。
  •   
  • 授予方法包访问权。
  •   
  • 使用嵌套测试类。
  •   
  • 使用反射。
  •   

答案 5 :(得分:111)

通常,单元测试旨在运用类或单元的公共接口。因此,私有方法是您不希望明确测试的实现细节。

答案 6 :(得分:62)

我想要测试私有方法的两个例子:

  1. 解密程序 - 我不会 想要让任何人都能看到它们 为了测试,其他任何人都可以 用它们来解密。但他们是 代码固有的,复杂的, 并且需要始终工作(明显的例外是反射,在大多数情况下,当SecurityManager未配置为阻止此情况时),甚至可以用于查看私有方法。
  2. 为社区
  3. 创建SDK 消费。在这里,公众接受了 完全不同的意思,因为这个 是全世界都可以看到的代码 (不仅仅是我的应用程序的内部)。我放 如果我没有代码到私人方法 希望SDK用户看到它 - 我 不要把这看作是代码味道 与SDK编程如何工作。但是 当然我还需要测试我的 私人方法,它们就在哪里 实际上我的SDK的功能 生活。
  4. 我理解只测试“合同”的想法。但是我没有看到人们可以提倡实际上没有测试代码 - 你的里程可能会有所不同。

    所以我的权衡涉及将JUnits与反射复杂化,而不是损害我的安全性和安全性。 SDK。

答案 7 :(得分:54)

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

答案 8 :(得分:34)

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

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

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

答案 9 :(得分:32)

为了测试具有大型和古怪类的遗留代码,能够测试我正在编写目前的的一个私有(或公共)方法通常非常有用。

我使用 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);

答案 10 :(得分:26)

尝试过Cem Catikkas的solution using reflection用于Java,我不得不说他的解决方案比我在这里描述的更优雅。但是,如果您正在寻找使用反射的替代方法,并且可以访问您正在测试的源,那么这仍然是一个选项。

测试类的私有方法有可能是有用的,特别是对于test-driven development,您希望在编写任何代码之前设计小型测试。

创建可访问私有成员和方法的测试可以测试难以专门定位的代码区域,只能访问公共方法。如果公共方法涉及多个步骤,则它可以包含多个私有方法,然后可以单独进行测试。

优点:

  • 可以测试更精细的粒度

缺点:

  • 测试代码必须位于同一个代码中 文件作为源代码,可以 更难维护
  • 与.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 Tip 106: Static inner classes for fun and profit

答案 11 :(得分:23)

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

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

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

我个人认为使用TDD创建类更好;创建公共方法存根,然后使用所有预先定义的断言生成单元测试,因此在编码之前确定方法的预期结果。这样,您就不会走错路径,使单元测试断言符合结果。您的课程非常强大,并且在所有单元测试通过后都符合要求。

答案 12 :(得分:23)

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

ReflectionTestUtils.invokeMethod()

例如:

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

答案 13 :(得分:23)

正如其他人所说......不要直接测试私人方法。以下是一些想法:

  1. 保持所有方法小巧且专注(易于测试,易于发现错误)
  2. 使用代码覆盖工具。我喜欢Cobertura(哦,快乐的一天,看起来像是一个新版本!)
  3. 在单元测试中运行代码覆盖率。如果您发现方法未经过全面测试,请添加测试以获得覆盖率。瞄准100%的代码覆盖率,但意识到你可能不会得到它。

答案 14 :(得分:22)

如果使用Spring,ReflectionTestUtils提供了一些方便的工具,只需很少的工作就可以帮助您。例如,要在私有成员上设置模拟而不必强制添加不需要的公共setter:

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

答案 15 :(得分:21)

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

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

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

答案 16 :(得分:16)

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

所以不要测试私有方法。

答案 17 :(得分:15)

如果要测试无法更改代码的旧应用程序的私有方法,Java的一个选项是jMockit,这样就可以为对象创建模拟,即使它们是私有的上课。

答案 18 :(得分:15)

JUnit.org FAQ page的回答:

  

但如果你必须......

     

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

     

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

P.S。我是Dp4j的主要撰稿人,如果您需要帮助,请问me。 :)

答案 19 :(得分:13)

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

答案 20 :(得分:13)

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

答案 21 :(得分:12)

我建议你稍微重构一下你的代码。当你必须开始考虑使用反射或其他类型的东西时,只是为了测试代码,你的代码就会出错。

您提到了不同类型的问题。让我们从私人领域开始。在私有字段的情况下,我会添加一个新的构造函数并将注入的字段注入其中。而不是:

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

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

现在关于私人方法。根据我个人的经验,当您必须存根私有方法进行测试时,该方法与该类无关。在这种情况下,一个常见的模式是在接口中 wrap ,如Callable,然后你也在构造函数中传入该接口(使用那个多个构造函数): / p>

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

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

我写的大部分内容看起来都是依赖注入模式。根据我的个人经验,它在测试时非常有用,我认为这种代码更清晰,更易于维护。关于嵌套类,我会说同样的。如果嵌套类包含重逻辑,那么如果将它作为包私有类移动并将其注入需要它的类中会更好。

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

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

答案 22 :(得分:12)

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

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

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

答案 23 :(得分:11)

正如上面提到的那样,一个好方法是通过公共接口测试它们。

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

答案 24 :(得分: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);
}

答案 25 :(得分:10)

请参阅下面的示例;

应添加以下import语句:

import org.powermock.reflect.Whitebox;

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

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

答案 26 :(得分: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

答案 27 :(得分:8)

首先,我会抛出这个问题:为什么你的私人成员需要隔离测试?它们是否复杂,提供如此复杂的行为以至于需要在公共场所进行测试?它是单元测试,而不是“代码行”测试。不要让小东西流汗。

如果它们那么大,足够大,以至于这些私人成员都是一个复杂性很大的“单位” - 考虑重构这些私人成员。

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

答案 28 :(得分: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));

答案 29 :(得分:6)

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

  

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

事实并非如此。你肯定可以,如Cem Catikkas's answer中提到的那样。

答案 30 :(得分:6)

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

答案 31 :(得分:4)

在c ++中:在包含具有您想要测试它的私有函数的类头之前

使用此代码:

#define private public
#define protected public

答案 32 :(得分:3)

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

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

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

答案 33 :(得分:3)

使用ExpectedException时,快速添加@Cem Catikka的评论:

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

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

答案 34 :(得分:3)

您可以关闭反射的Java访问限制,这样私有就意味着什么。

setAccessible(true)来电就是这样做的。

唯一的限制是ClassLoader可能会禁止您这样做。

请参阅 Subverting Java Access Protection for Unit Testing (Ross Burton)了解在Java中执行此操作的方法。

答案 35 :(得分:3)

我想通过不像许多帖子建议的那样测试私有方法来担心您的担心,考虑到代码覆盖率工具将确切地确定您的代码的数量是多少测试和泄漏的地方,所以这是可以量化的。

我想说些什么需要说,但很多人可能不喜欢听。你们中的那些人提供了答案,指导问题的作者进行“工作”。对社区造成了巨大的伤害。测试是所有工程学科的主要部分,您不希望购买未经适当测试的汽车,并以有意义的方式进行测试,那么为什么有人想要购买或使用经过测试的软件呢?无论如何人们这样做的原因可能是因为经过严格测试的软件的效果在事后才能感觉到,而且我们通常不会将它们与身体伤害联系在一起。这是一种非常危险的感知,难以改变,但无论管理层欺负我们做什么,我们都有责任提供安全的产品。想想Equifax hack ......

我们必须努力建立一个鼓励良好的软件工程实践的环境,这并不意味着排斥那些不认真对待他们的技术的弱者/懒惰,而是创造一种责任和自我反思的现状。鼓励每个人在精神上和技巧上追求成长。我仍在学习,自己可能有错误的观点/意见,但我坚信我们需要对良好做法负责,并避免不负责任的黑客或问题的解决方法。

答案 36 :(得分:3)

如果您在 spring 上,请使用此实用工具类。

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

答案 37 :(得分:2)

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

但是当然在另一个目录中src&amp; classes代表您的测试类的源代码test/srctest/classes。让classestest/classes在您的类路径中。

答案 38 :(得分:2)

在C#中你可以使用System.Reflection,虽然在Java中我不知道。虽然我觉得无论如何都要回答这个问题,因为如果你“觉得你需要对私人方法进行单元测试”,我的猜测就是有其他错误......

我会认真地考虑再次用新鲜的眼睛看待我的建筑......

答案 39 :(得分:2)

如下所示,用龙目岛详细说明我的样品。私有字段,私有方法:

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
@ToString
class Student {
  private String name;
  private String age;

  private String getInfo(String name, String age) {
    return name + "-" + age;
  }
}

答案 40 :(得分:2)

从测试框架测试Java私有方法的最佳和合法的合法方法是 @VisibleForTesting 对方法的注释,因此对于测试框架,同样的方法将像公共方法一样可见。

答案 41 :(得分:1)

我和我的团队正在使用Typemock,它具有一个API,可让您fake non-public methods. 最近,他们在fake non-visible types中添加了使用XUnit的功能。

答案 42 :(得分:1)

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

<块引用>

我认为你永远不应该为了 放纵轻松编写测试。

在其他帖子中有一些建议可以调整原始类以测试私有方法。如果您将方法/字段的可访问性更改为包私有或受保护,只是为了让测试可以访问它,那么您就违背了私有访问指令存在的目的。

如果我们想要测试驱动的开发,为什么还要有私有字段/方法/类?我们是否应该将所有内容都声明为私有包,甚至是公开包,以便我们可以毫不费力地进行测试? - 我不这么认为。

从另一个角度来看。测试不应加重生产应用程序的性能和执行。如果您只是为了更容易测试而更改生产代码,那么您可能会以某种方式加重性能和应用程序的执行。而且,如果您开始将私有访问权限更改为包私有,您最终可能会想到向原始类添加更多代码的“巧妙想法”,这会给可读性带来额外干扰,并可能加重应用程序的性能。

另一方面,随着将私有访问更改为一些限制较少的访问,您为开发人员在应用程序的未来开发中滥用新情况提供了可能性。你不是强迫他以正确的方式发展,而是为他打开了新的可能性,让他有能力在未来做出错误的选择。

当然,这条规则可能有一些例外,但要清楚地了解什么是规则,什么是例外以及为什么要这样做。

答案 43 :(得分:1)

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

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

我有一个繁重的应用程序,可以在超过一百万人的人口中模拟人类行为。每个人都由一个对象表示。该应用程序的主要目的是跟踪某物(疾病、信息、想法)如何在人群中传播。

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

但我想确定的是,将一点信息从一个人传递给另一个人的单一行为是否正在做它应该做的事情。通过公共用户界面对其进行测试也是不可能的,因为它只是不公开的,而且我认为仅出于测试目的而将其公开是很尴尬的。应用程序的最终输出由一直执行的数亿个此类单个步骤定义。我也无法测试最终输出,因为这涉及复杂的随机稳定性,这使得测试太不可预测。

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

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

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

答案 44 :(得分:1)

如果您确实需要直接测试私有方法/类等,则可以使用其他答案中已经提到的反射。但是,如果涉及到这一点,我宁愿使用您的框架提供的util类,也不要直接处理反射。例如,对于Java,我们有:

根据使用方法,您可以在线找到大量文章。这是我特别喜欢的一个:

答案 45 :(得分:1)

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

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

http://www.eecs.ucf.edu/~leavens/JML/jmlrefman/jmlrefman_2.html#SEC12讨论了这种语法。还存在一个将JML规范转换为JUnit测试的程序。我不确定它的效果如何或它的功能是什么,但它似乎没有必要,因为JML本身就是一个可行的测试框架。

答案 46 :(得分:1)

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

EG。设置私有方法的返回值:

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

最后,您可以验证您的私有方法是否正在根据上面的设置值返回正确的值:

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

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

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

答案 47 :(得分:0)

您可以创建一个特殊的公共方法来代理私有方法进行测试。 使用IntelliJ时,@ TestOnly注释是开箱即用的。 缺点是,如果有人想在公共环境中使用私有方法,他可以做到。但是注释和方法名称将警告他。在IntelliJ上,执行此操作时将显示警告。

import org.jetbrains.annotations.TestOnly

class MyClass {

    private void aPrivateMethod() {}

    @TestOnly
    public void aPrivateMethodForTest() {
        aPrivateMethod()
    }
}

答案 48 :(得分:0)

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

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

答案 49 :(得分:0)

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

  • 使用PowerMock测试私有内部状态(字段),或仅进行反射而无需引入另一个独立性的开销
  • 对于私有方法:实际上,对该问题本身的支持以及大量评论和答案表明,这是一个非常并行且有争议的主题,在此情况下,无法给出适合每种情况的确切答案< / strong>。我知道,只有合同应进行测试,但我们也要考虑范围。实际上,我怀疑只有测试合同才能使100%的课程不受错误影响。私有方法是那些在定义了数据的类中处理数据的人,因此对其他类不感兴趣,因此我们不能简单地公开使其可测试。我将尝试不对其进行测试,但是,当您必须这样做时,就去做吧,并忘记这里的所有答案。您比互联网上的其他任何人都更加了解自己的处境和限制。当您控制代码时,请使用它。经过考虑,但没有过多考虑。

答案 50 :(得分:0)

Android的android.support.annotation包中有@VisibleForTesting个注释。

  

@VisibleForTesting批注指示带注释的方法比使该方法可测试所需的正常显示更具可见性。该注释具有一个可选的otherwise参数,通过该参数,您可以指定该方法的可见性(如果不是为了使其在测试中可见)应具有的可见性。 Lint使用otherwise参数来增强预期的可见性。

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

例如

package com.mypackage;

public class ClassA {

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    static void myMethod() {

    }
}

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

enter image description here

答案 51 :(得分:0)

对于C ++(自C ++ 11起),将测试类添加为好友非常有效,并且不会破坏生产封装。

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

// prod.h: some production code header

// forward declaration is enough
// we should not include testing headers into production code
class FooTest;

class Foo
{
  // that does not affect Foo's functionality
  // but now we have access to Foo's members from FooTest
  friend 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!
}

答案 52 :(得分:0)

以下 Reflection TestUtil 可以 通用 来测试 私有方法的原子性

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)

答案 53 :(得分:0)

如果您仅使用Mockito:

您可以将私有方法视为正在测试的公共方法的一部分。测试公共方法时,可以确保用私有方法覆盖所有情况。

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

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

这允许您模拟这些对象并将它们注入回测试对象。您也可以嘲笑(使用when / then)他们的方法。

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

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

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

答案 54 :(得分:0)

在您的课堂上:

namespace my_namespace {
    #ifdef UNIT_TEST
        class test_class;
    #endif

    class my_class {
        public:
            #ifdef UNIT_TEST
                friend class test_class;
            #endif
        private:
            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 ;)
}

答案 55 :(得分:0)

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

我尽量减少对此的需求,并且我总是记录避免混淆的确切原因。

答案 56 :(得分:0)

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