1.概述

在这个教程中,我们将简要解释为什么直接测试私有方法通常是不明智的,并将展示在必要时如何在Java中测试私有方法。

2.为什么不应该测试私有方法

通常情况下,我们编写的单元测试应该只检查公开方法的契约。私有方法是我们的公共方法调用者不知道的实现细节。此外,改变实现细节不应当导致我们需要修改测试。

一般来说,想要测试一个私有方法可能表明以下问题之一:

  • 私有方法中有死代码。
  • 私有方法过于复杂,应该属于另一个类。
  • 这个方法最初就不应该被标记为私有。

因此,当我们觉得需要测试一个私有方法时,真正应该做的是修复底层设计问题。

3.示例:从私有方法中移除死代码

让我们通过一个快速示例来展示这一点。

我们将编写一个私有方法,它会返回一个Integer的两倍。对于null值,我们希望返回null

private static Integer doubleInteger(Integer input) {
    if (input == null) {
        return null;
    }
    return 2 * input;
}

现在,让我们编写我们的公共方法。这将是类外部的唯一入口点。

这个方法接收一个Integer作为输入。如果输入为null,它将抛出一个IllegalArgumentException。然后,它调用私有方法来返回Integer值的两倍:

public static Integer validateAndDouble(Integer input) {
    if (input == null) {
        throw new IllegalArgumentException("input should not be null");
    }
    return doubleInteger(input);
}

遵循良好的实践,我们应该测试我们的公共方法契约。

首先,让我们写一个测试,确保当输入为null时,会抛出IllegalArgumentException

@Test
void givenNull_WhenValidateAndDouble_ThenThrows() {
    assertThrows(IllegalArgumentException.class, () -> validateAndDouble(null));
}

接下来,检查非nullInteger是否正确翻倍:

@Test
void givenANonNullInteger_WhenValidateAndDouble_ThenDoublesIt() {
    assertEquals(4, validateAndDouble(2));
}

让我们看看JaCoCo插件报告的覆盖率:

方法覆盖报告如我们所见,私有方法中的null检查没有被我们的单元测试覆盖。那么我们应该测试它吗?

答案是不。重要的是要理解,我们的私有方法并非孤立存在。它只会在我方公共方法验证数据后被调用。因此,私有方法中的null检查永远不会被触发,它是死代码,应该被删除。

4.如何在Java中测试私有方法

假设我们并未被劝阻,让我们具体说明如何在Java中测试私有方法。

为了测试它,如果我们的私有方法具有另一种可见性将会很有帮助。好消息是,我们可以使用反射来模拟这一点。

我们的封装类称为Utils。想法是访问名为doubleInteger的私有方法,它接受一个Integer作为参数。然后,我们将其可见性改为可以从Utils类外部访问。让我们看看如何做到这一点:

private Method getDoubleIntegerMethod() throws NoSuchMethodException {
    Method method = Utils.class.getDeclaredMethod("doubleInteger", Integer.class);
    method.setAccessible(true);
    return method;
}

现在我们可以使用这个方法了。让我们写一个测试,确保给定null对象时,我们的私有方法返回null。我们需要对一个将为null的参数应用此方法:

@Test
void givenNull_WhenDoubleInteger_ThenNull() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
    assertEquals(null, getDoubleIntegerMethod().invoke(null, new Integer[] { null }));
}

关于invoke方法的使用,第一个参数是我们应用方法的对象。由于doubleInteger是静态的,我们传递了一个null。第二个参数是一个参数数组。在这种情况下,我们只有一个参数,且它是null

最后,让我们演示如何测试非null输入的情况:

@Test
void givenANonNullInteger_WhenDoubleInteger_ThenDoubleIt() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    assertEquals(74, getDoubleIntegerMethod().invoke(null, 37));
}

5.总结

在这篇文章中,我们了解了为什么通常不建议测试私有方法,以及如何在Java中使用反射来测试私有方法。如往常一样,代码可在GitHub上找到。