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

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


当前回答

这是我的龙目样本:

public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
    Student student = new Student();

    Field privateFieldName = Student.class.getDeclaredField("name");
    privateFieldName.setAccessible(true);
    privateFieldName.set(student, "Naruto");

    Field privateFieldAge = Student.class.getDeclaredField("age");
    privateFieldAge.setAccessible(true);
    privateFieldAge.set(student, "28");

    System.out.println(student.toString());

    Method privateMethodGetInfo = Student.class.getDeclaredMethod("getInfo", String.class, String.class);
    privateMethodGetInfo.setAccessible(true);
    System.out.println(privateMethodGetInfo.invoke(student, "Sasuke", "29"));
}


@Setter
@Getter
@ToString
class Student {
    private String name;
    private String age;
    
    private String getInfo(String name, String age) {
        return name + "-" + age;
    }
}

其他回答

如果您正在使用JUnit,请查看JUnit插件。它能够忽略Java安全模型并访问私有方法和属性。

您可以关闭反射的Java访问限制,这样私有就没有什么意义了。

setAccessible(true)调用实现了这一点。

唯一的限制是ClassLoader可能不允许您这样做。

有关在Java中实现这一点的方法,请参阅颠覆单元测试的Java访问保护(Ross Burton)。

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

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

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

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

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

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

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

请参见下面的示例;

应添加以下导入语句:

import org.powermock.reflect.Whitebox;

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

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