如何使用JUnit测试具有内部私有方法、字段或嵌套类的类?
仅仅为了能够运行测试而更改方法的访问修饰符似乎是不好的。
如何使用JUnit测试具有内部私有方法、字段或嵌套类的类?
仅仅为了能够运行测试而更改方法的访问修饰符似乎是不好的。
当前回答
我想分享一条关于测试的规则,这条规则尤其与本主题相关:
我认为你不应该为了更轻松地编写测试。
在其他帖子中有一些建议,说你应该调整原始类以测试私有方法-请先将此警告标记为红色。
如果我们将方法/字段的可访问性更改为包私有或受保护,只是为了让测试可以访问它,那么我们就违背了私有访问指令存在的目的。
当我们想要进行测试驱动的开发时,为什么要有私有字段/方法/类?那么,我们是否应该将所有内容都声明为私有的,甚至是公共的,这样我们就可以不费力地进行测试了-我不这么认为。
从另一个角度来看:测试不应影响生产应用程序的性能和执行。
如果我们仅仅为了更容易的测试而更改生产代码,这可能会在某种程度上影响性能和应用程序的执行。
如果有人开始将私有访问更改为包私有,那么开发人员最终可能会想出其他“巧妙的想法”,向原始类添加更多代码。这会给可读性带来额外的噪音,并会影响应用程序的性能。
随着私有访问更改为限制性较小的访问,我们为开发人员在应用程序的未来开发中滥用新情况提供了可能性。我们不是强迫他/她以正确的方式发展,而是用新的可能性引诱他/她,让他/她有能力在未来做出错误的选择。
当然,这条规则可能会有一些例外,但如果理解清楚,规则是什么,例外是什么?我们需要绝对确定我们知道为什么会引入这种例外。
其他回答
从本文:使用JUnit和SuiteRunner测试私有方法(Bill Venners)中,您基本上有4个选项:
不要测试私有方法。授予方法包访问权限。使用嵌套测试类。使用反射。
我感觉完全一样。。。更改一个方法的访问修饰符,以便能够运行测试,这对我来说是个坏主意。在我们公司,我们也进行了很多讨论,在我看来,测试私有方法的好方法是使用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()”的测试失败,那么很清楚在哪里查找错误。
您可以关闭反射的Java访问限制,这样私有就没有什么意义了。
setAccessible(true)调用实现了这一点。
唯一的限制是ClassLoader可能不允许您这样做。
有关在Java中实现这一点的方法,请参阅颠覆单元测试的Java访问保护(Ross Burton)。
私有方法被公共方法使用。否则,它们就是死代码。这就是为什么要测试公共方法,断言公共方法的预期结果,从而断言它使用的私有方法。
在公共方法上运行单元测试之前,应该通过调试来测试私有方法。
也可以使用测试驱动开发来调试它们,调试单元测试,直到满足所有断言。
我个人认为最好使用TDD创建类;创建公共方法存根,然后使用预先定义的所有断言生成单元测试,这样在编写代码之前就可以确定方法的预期结果。这样,就不会走上使单元测试断言符合结果的错误道路。然后,当所有单元测试通过时,您的类是健壮的并满足要求。
如果您试图测试您不愿意或无法更改的现有代码,则反射是一个不错的选择。
如果类的设计仍然是灵活的,并且您有一个复杂的私有方法需要单独测试,我建议您将其导出到一个单独的类中,并单独测试该类。这不需要更改原始类的公共接口;它可以在内部创建helper类的实例并调用helper方法。
如果您想测试来自helper方法的困难错误条件,可以更进一步。从helper类中提取一个接口,向原始类中添加一个公共getter和setter以注入helper类(通过其接口使用),然后将helper类的模拟版本注入原始类中,以测试原始类如何响应helper的异常。如果您想测试原始类而不同时测试助手类,这种方法也很有用。