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

仅仅为了能够运行测试而更改方法的访问修饰符似乎是不好的。


当前回答

如果使用Spring,请使用此实用程序类:

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

其他回答

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

我不修改要测试的方法的可见性,也不添加其他方法。相反,我为要测试的每个私有方法添加了一个额外的公共方法。我调用这个额外的方法TestPort,并用前缀t_表示它们。然后,此测试端口方法只需访问相应的私有方法。

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

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

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

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

这不是真的。你当然可以,正如塞姆·卡提卡斯的回答中所提到的。

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

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

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注入,但我不建议使用它。我最好使用构造函数,并在真正需要时初始化所有内容,留下注入必要依赖项的可能性。

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