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

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


当前回答

下面的ReflectionTestUtil可以通用地用于测试私有方法的原子性。

import com.google.common.base.Preconditions;

import org.springframework.test.util.ReflectionTestUtils;

/**
 * <p>
 * Invoker
 * </p>
 *
 * @author
 * @created Oct-10-2019
 */
public class Invoker {
    private Object target;
    private String methodName;
    private Object[] arguments;

    public <T> T invoke() {
        try {
            Preconditions.checkNotNull(target, "Target cannot be empty");
            Preconditions.checkNotNull(methodName, "MethodName cannot be empty");
            if (null == arguments) {
                return ReflectionTestUtils.invokeMethod(target, methodName);
            } else {
                return ReflectionTestUtils.invokeMethod(target, methodName, arguments);
            }
        } catch (Exception e) {
           throw e;
        }
    }

    public Invoker withTarget(Object target) {
        this.target = target;
        return this;
    }

    public Invoker withMethod(String methodName) {
        this.methodName = methodName;
        return this;
    }

    public Invoker withArguments(Object... args) {
        this.arguments = args;
        return this;
    }

}

Object privateMethodResponse = new Invoker()
  .withTarget(targetObject)
  .withMethod(PRIVATE_METHOD_NAME_TO_INVOKE)
  .withArguments(arg1, arg2, arg3)
  .invoke();
Assert.assertNotNutll(privateMethodResponse)

其他回答

这是我的龙目样本:

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

Groovy有一个bug/特性,通过它可以调用私有方法,就像它们是公共的一样。因此,如果您能够在项目中使用Groovy,那么可以使用它来代替反射。查看此页面以获取示例。

如果您试图测试您不愿意或无法更改的现有代码,则反射是一个不错的选择。

如果类的设计仍然是灵活的,并且您有一个复杂的私有方法需要单独测试,我建议您将其导出到一个单独的类中,并单独测试该类。这不需要更改原始类的公共接口;它可以在内部创建helper类的实例并调用helper方法。

如果您想测试来自helper方法的困难错误条件,可以更进一步。从helper类中提取一个接口,向原始类中添加一个公共getter和setter以注入helper类(通过其接口使用),然后将helper类的模拟版本注入原始类中,以测试原始类如何响应helper的异常。如果您想测试原始类而不同时测试助手类,这种方法也很有用。

在尝试了Cem Catikkas使用Java反射的解决方案后,我不得不说,他的解决方案比我在这里描述的更优雅。然而,如果您正在寻找使用反射的替代方案,并且能够访问您正在测试的源代码,那么这仍然是一个选项。

测试类的私有方法可能有好处,特别是在测试驱动开发中,您希望在编写任何代码之前设计小型测试。

创建一个可以访问私有成员和方法的测试,可以测试那些只访问公共方法而难以专门针对的代码区域。如果公共方法涉及多个步骤,它可以由多个私有方法组成,然后可以单独测试。

优势:

可以测试到更精细的粒度

缺点:

测试代码必须位于文件作为源代码更难维护与.class输出文件类似,它们必须保持在源代码中声明的相同包中

然而,如果连续测试需要这种方法,这可能是一个信号,表明应该提取私有方法,可以以传统的公共方式进行测试。

下面是一个复杂的例子,说明这是如何工作的:

// Import statements and package declarations

public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }

    // Constructor and the rest of the class

    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }

        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

内部类将编译为ClassToTest$StaticInnerTest。

另请参阅:Java提示106:静态内部类以获取乐趣和利润

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

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

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

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

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