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

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


当前回答

如果你担心没有像很多帖子所建议的那样测试私有方法,那么考虑一下代码覆盖工具将准确地确定你的代码被测试了多少以及泄漏的地方,所以这样做是可以接受的。

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

这是一种非常危险的观念,很难改变,但我们有责任提供安全的产品,而不管管理层如何欺负我们。想想Equifax黑客。。。

我们必须努力营造一个鼓励良好软件工程实践的环境。这并不意味着排斥我们中那些不认真对待自己手艺的弱者/懒惰者,而是创造一种责任感和自我反思的现状,鼓励每个人在精神和技能上追求成长。

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

其他回答

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

解密例程-我不会想让任何人都能看到它们为了测试,否则任何人都可以使用它们来解密。但他们是代码固有的、复杂的,并且需要始终工作(明显的例外是反射,在大多数情况下,当SecurityManager未配置为防止这种情况发生时,反射甚至可以用于查看私有方法)。为社区创建SDK消耗在这里,公众对完全不同的含义,因为是全世界都能看到的代码(不仅仅是我的应用程序内部)。我把如果我不这样做,请将代码转换为私有方法希望SDK用户看到它-我不要将其视为代码气味SDK编程是如何工作的。但是当然我还需要测试私有方法,它们在哪里我的SDK的功能生活。

我理解只测试“合同”的想法。但我看不出有人可以主张不测试代码,因为您的里程可能会有所不同。

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

如果您只使用Mockito:

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

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

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

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

这将允许您在测试公共方法时无错误地测试私有方法。

优势

代码覆盖范围能够测试完整的私有方法。

缺点

对象的作用域如果您不希望对象公开给同一类中的其他方法,这可能不是您的方式。当在不同的公共方法和/或在同一方法中多次调用时,您可能会多次测试私有方法。

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

所以不要测试私有方法。

请参见下面的示例;

应添加以下导入语句:

import org.powermock.reflect.Whitebox;

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

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

我感觉完全一样。。。更改一个方法的访问修饰符,以便能够运行测试,这对我来说是个坏主意。在我们公司,我们也进行了很多讨论,在我看来,测试私有方法的好方法是使用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()”的测试失败,那么很清楚在哪里查找错误。