如何使用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;
    }
}

其他回答

我想分享一条关于测试的规则,这条规则尤其与本主题相关:

我认为你不应该为了更轻松地编写测试。

在其他帖子中有一些建议,说你应该调整原始类以测试私有方法-请先将此警告标记为红色。

如果我们将方法/字段的可访问性更改为包私有或受保护,只是为了让测试可以访问它,那么我们就违背了私有访问指令存在的目的。

当我们想要进行测试驱动的开发时,为什么要有私有字段/方法/类?那么,我们是否应该将所有内容都声明为私有的,甚至是公共的,这样我们就可以不费力地进行测试了-我不这么认为。

从另一个角度来看:测试不应影响生产应用程序的性能和执行。

如果我们仅仅为了更容易的测试而更改生产代码,这可能会在某种程度上影响性能和应用程序的执行。

如果有人开始将私有访问更改为包私有,那么开发人员最终可能会想出其他“巧妙的想法”,向原始类添加更多代码。这会给可读性带来额外的噪音,并会影响应用程序的性能。

随着私有访问更改为限制性较小的访问,我们为开发人员在应用程序的未来开发中滥用新情况提供了可能性。我们不是强迫他/她以正确的方式发展,而是用新的可能性引诱他/她,让他/她有能力在未来做出错误的选择。

当然,这条规则可能会有一些例外,但如果理解清楚,规则是什么,例外是什么?我们需要绝对确定我们知道为什么会引入这种例外。

下面的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)

我使用的另一种方法是将私有方法更改为打包私有或受保护的方法,然后使用GoogleGuava库的@VisibleForTesting注释对其进行补充。

这将告诉使用此方法的任何人要小心,即使在包中也不要直接访问它。同样,测试类不需要在物理上位于同一个包中,而是位于测试文件夹下的同一包中。

例如,如果要测试的方法位于src/main/java/mybackage/MyClass.java中,那么您的测试调用应该位于src/test/java/mypackage/MiClassTest.java中。这样,您就可以访问测试类中的测试方法。

今天,我推出了一个Java库来帮助测试私有方法和字段。它的设计考虑到了Android,但它确实可以用于任何Java项目。

如果您有一些带有私有方法、字段或构造函数的代码,可以使用BoundBox。它正是你想要的。下面是一个测试示例,它访问Android活动的两个私有字段来测试它:

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

BoundBox使测试私有/受保护的字段、方法和构造函数变得容易。你甚至可以访问被继承隐藏的东西。实际上,BoundBox打破了封装。它会让您通过反射访问所有这些内容,但在编译时会检查所有内容。

它非常适合测试一些遗留代码。小心使用。;)

PowerMockito就是为此而生的。

使用Maven依赖项:

    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-core</artifactId>
        <version>2.0.7</version>
        <scope>test</scope>
    </dependency>

那你就可以了

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);