Java 如何测试私有函数或具有私有方法、字段或内部类的类?

Java 如何测试私有函数或具有私有方法、字段或内部类的类?,java,unit-testing,tdd,Java,Unit Testing,Tdd,如何(使用xUnit)对具有内部私有方法、字段或嵌套类的类进行单元测试?或者是通过在C/C++中使用(static)使函数私有化的函数,或者是在私有()命名空间中的函数 仅仅为了能够运行测试而更改方法或函数的访问修饰符似乎不好 测试私有方法的最佳方法是通过另一个公共方法。如果无法执行此操作,则以下条件之一为真: 私有方法是死代码 您正在测试的类附近有一种设计气味 您尝试测试的方法不应是私有的 通常情况下,单元测试旨在实现类或单元的公共接口。因此,私有方法是您不希望显式测试的实现细节。私有方法由公

如何(使用xUnit)对具有内部私有方法、字段或嵌套类的类进行单元测试?或者是通过在C/C++中使用(
static
)使函数私有化的函数,或者是在私有()命名空间中的函数


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

测试私有方法的最佳方法是通过另一个公共方法。如果无法执行此操作,则以下条件之一为真:

  • 私有方法是死代码
  • 您正在测试的类附近有一种设计气味
  • 您尝试测试的方法不应是私有的

  • 通常情况下,单元测试旨在实现类或单元的公共接口。因此,私有方法是您不希望显式测试的实现细节。

    私有方法由公共方法调用,因此公共方法的输入也应该测试这些公共方法调用的私有方法。当公共方法失败时,则可能是私有方法失败。

    本文:(Bill Venners),您基本上有4个选项:

  • 不要测试私有方法
  • 提供方法包访问权限
  • 使用嵌套的测试类
  • 使用反射

  • 我倾向于不测试私有方法。疯狂就在那里。我个人认为,您应该只测试公开的接口(包括受保护的和内部的方法)

    更新:

    大约10年后,也许测试私有方法或任何无法访问的成员的最佳方法是通过框架中的越狱

    @Jailbreak Foo foo = new Foo();
    // Direct, *type-safe* access to *all* foo's members
    foo.privateMethod(x, y, z);
    foo.privateField = value;
    
    这样,代码就保持了类型安全和可读性。没有设计妥协,没有过度曝光的方法和领域的测试

    如果您有一些遗留的Java应用程序,并且不允许更改方法的可见性,那么测试私有方法的最佳方法就是使用

    在内部,我们使用助手获取/设置
    private
    private static
    变量,以及调用
    private
    private static
    方法。下面的模式将允许您执行几乎所有与私有方法和字段相关的操作。当然,您不能通过反射来更改
    私有静态final
    变量

    Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
    method.setAccessible(true);
    return method.invoke(targetObject, argObjects);
    
    对于字段:

    Field field = TargetClass.getDeclaredField(fieldName);
    field.setAccessible(true);
    field.set(object, value);
    

    备注:
    1.
    TargetClass.getDeclaredMethod(methodName,argClasses)
    允许您查看
    private
    方法。同样的事情也适用于
    getDeclaredField

    2.使用
    setAccessible(true)
    与私家车玩耍是必需的


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

    测试类的私有方法可能有好处,特别是在编写任何代码之前,您希望设计小型测试的情况下

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

    优点:

    • 可以测试到更精细的粒度
    缺点:

    • 测试代码必须驻留在同一数据库中 文件作为源代码,可以 更难维护
    • 与.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


    另请参见:

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

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


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

    如果您想测试无法更改代码的遗留应用程序的私有方法,Java的一个选项是,这将允许您创建对象的模拟,即使它们是类的私有对象。

    当我在一个类中有足够复杂的私有方法,我觉得需要直接测试私有方法时,这是一种代码味道:我的类太复杂了

    我解决这些问题的通常方法是梳理出一个包含有趣部分的新类。通常,此方法及其交互的字段,以及其他一两个方法可以提取到一个新类中

    新类将这些方法公开为“public”,因此可以对它们进行单元测试。新老cla
    import junitx.util.PrivateAccessor;
    
    PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
    PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);
    
    @UiThreadTest
    public void testCompute() {
    
        // Given
        boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());
    
        // When
        boundBoxOfMainActivity.boundBox_getButtonMain().performClick();
    
        // Then
        assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
    }
    
    protected <F> F getPrivateField(String fieldName, Object obj)
        throws NoSuchFieldException, IllegalAccessException {
        Field field =
            obj.getClass().getDeclaredField(fieldName);
    
        field.setAccessible(true);
        return (F)field.get(obj);
    }
    
    ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);
    
    Method method = targetClass.getDeclaredMethod("method", String.class);
    method.setAccessible(true);
    return method.invoke(targetObject, "mystring");
    
    interface Accessible {
      void method(String s);
    }
    
    ...
    Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
    a.method("mystring");
    
    Field field = targetClass.getDeclaredField("amount");
    field.setAccessible(true);
    field.set(object, BigInteger.valueOf(42));
    
    interface Accessible {
      void setAmount(BigInteger amount);
    }
    
    ...
    Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
    a.setAmount(BigInteger.valueOf(42));
    
    ReflectionTestUtils.invokeMethod()
    
    ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");
    
    import org.powermock.reflect.Whitebox;
    
    Whitebox.invokeMethod(obj, "privateMethod", "param1");
    
    public class ClassToTest {
    
        private final String first = "first";
        private final List<String> second = new ArrayList<>();
        ...
    }
    
    public class ClassToTest {
    
        private final String first;
        private final List<String> second;
    
        public ClassToTest() {
            this("first", new ArrayList<>());
        }
    
        public ClassToTest(final String first, final List<String> second) {
            this.first = first;
            this.second = second;
        }
        ...
    }
    
    public ClassToTest() {
        this(...);
    }
    
    public ClassToTest(final Callable<T> privateMethodLogic) {
        this.privateMethodLogic = privateMethodLogic;
    }
    
        <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);