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

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


当前回答

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

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

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

其他回答

我感觉完全一样。。。更改一个方法的访问修饰符,以便能够运行测试,这对我来说是个坏主意。在我们公司,我们也进行了很多讨论,在我看来,测试私有方法的好方法是使用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”方法,我需要测试八个边缘情况和一些一般情况(直接位于角落的单元格、直接位于矩阵边缘的单元格和位于中间的单元格)。因此,如果我只是试图通过“computeNextGeneration”方法覆盖所有的情况,并且在重构之后,一些测试是红色的,那么识别错误所在的位置可能需要花费时间。

如果我分别测试“determineCellState”和“countNeighbors”,并且在重构和优化之后,“computeNextGeneration”和“determine CellState”的测试是红色的,那么我很确定错误将出现在“determiseCellState”方法中。

此外,如果您从一开始就为这些方法编写单元测试,这些测试将帮助您开发方法/算法,而无需考虑和包装公共方法中的其他方法调用和案例。你只需要在方法中编写快速的小测试来覆盖你的案例。。。例如,如果名为“countNeighbors_should_return_right_amount_of_noughbors_for_the_right_top_corner_cell()”的测试失败,那么很清楚在哪里查找错误。

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

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

MyClient classUnderTest = PowerMockito.spy(new MyClient());

// Set the expected return value
PowerMockito.doReturn(20).when(classUnderTest, "myPrivateMethod", anyString(), anyInt());
// This is very important. Otherwise, it will not work
classUnderTest.myPrivateMethod();

// Setting the private field value as someValue:
Whitebox.setInternalState(classUnderTest, "privateField", someValue);

最后,您可以通过以下方式验证您的私有方法:

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

Or

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

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

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

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

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

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

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