首页 文章

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

提问于
浏览
2379

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

为了能够运行测试而改变方法或函数的访问修饰符似乎很糟糕 .

30 回答

  • 241

    私有方法只能在同一个类中访问 . 因此,无法从任何测试类测试目标类的“私有”方法 . 一种解决方法是您可以手动执行单元测试,也可以将方法从“私有”更改为“受保护” .

    然后,只能在定义类的同一个包中访问受保护的方法 . 因此,测试目标类的受保护方法意味着我们需要在与目标类相同的包中定义测试类 .

    如果以上所有内容都不符合您的要求,请使用the reflection way访问私有方法 .

  • 10

    对于Java,我使用reflection,因为我不喜欢仅为了测试而在声明的方法上更改对包的访问的想法 . 但是,我通常只测试公共方法,这也应该确保私有方法正常工作 .

    您不能使用反射从所有者类外部获取私有方法,私有修饰符也会影响反射

    这不是真的 . 你肯定可以,如Cem Catikkas's answer中所述 .

  • 15

    我建议你稍微重构一下你的代码 . 当你必须开始考虑使用反射或其他类型的东西时,为了测试你的代码,你的代码就会出错 .

    你提到了不同类型的问题 . 让我们从私人领域开始 . 在私有字段的情况下,我会添加一个新的构造函数并将注入的字段注入其中 . 而不是这个:

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

    即使使用一些遗留代码,这也不会成为问题 . 旧代码将使用空构造函数,如果您问我,重构代码看起来更干净,并且您将能够在没有反射的情况下在测试中注入必要的值 .

    现在关于私人方法 . 根据我的个人经验,当您必须存根私有方法进行测试时,该方法与该类无关 . 在这种情况下,一个常见的模式是将它包装在一个接口中,如 Callable ,然后在构造函数中传入该接口(使用多个构造函数技巧):

    public ClassToTest() {
        this(...);
    }
    
    public ClassToTest(final Callable<T> privateMethodLogic) {
        this.privateMethodLogic = privateMethodLogic;
    }
    

    大多数我写的所有内容看起来都是依赖注入模式 . 根据我的个人经验,它在测试时非常有用,我认为这种代码更清晰,更易于维护 . 我会对嵌套类说同样的话 . 如果嵌套类包含重逻辑,那么如果将它作为包私有类移动并将其注入需要它的类中会更好 .

    我在重构和维护遗留代码时也使用了其他几种设计模式,但这完全取决于要测试的代码的情况 . 使用反射主要不是问题,但是当你有一个经过严格测试的企业应用程序并且在每次部署之前运行测试时,一切都变得非常慢(这很烦人,我不喜欢那种东西) .

    还有二次注射,但我不建议使用它 . 我最好坚持使用构造函数并在必要时初始化所有内容,留下注入必要依赖项的可能性 .

  • 21

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

    我解决这些问题的常用方法是梳理一个包含有趣位的新类 . 通常,此方法及其与之交互的字段,以及可能的另一种方法或两者可以被提取到新类中 .

    新类将这些方法公开为“公共”,因此它们可以进行单元测试 . 新旧课程现在都比原来的课程简单,这对我来说很棒(我需要保持简单,否则我会迷路!) .

    请注意,我并不是说人们在不使用大脑的情况下创建课程!这里的要点是使用单元测试的力量来帮助您找到好的新类 .

  • 10

    为了测试具有大型和古怪类的遗留代码,能够测试我现在正在编写的一个私有(或公共)方法通常非常有用 .

    我使用 junitx.util.PrivateAccessor -package for Java . 许多有用的单行程序用于访问私有方法和私有字段 .

    import junitx.util.PrivateAccessor;
    
    PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
    PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);
    
  • 11

    请看下面的例子;

    应添加以下import语句:

    import org.powermock.reflect.Whitebox;
    

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

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

    今天,我推出了一个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打破了封装 . 它将允许您通过反射访问所有内容, BUT 在编译时检查所有内容 .

    它是测试一些遗留代码的理想选择 . 仔细使用它 . ;)

    https://github.com/stephanenicolas/boundbox

  • 8

    首先,我会抛出这个问题:为什么你的私人成员需要隔离测试?它们是否复杂,提供如此复杂的行为以至于需要在公共场所进行测试?它是单元测试,而不是“代码行”测试 . 不要出汗小东西 .

    如果它们那么大,足够大,以至于这些私人成员都是一个复杂性很大的“单位” - 考虑重构这些私人成员 .

    如果重构不合适或不可行,您可以使用策略模式在单元测试下替换对这些私有成员函数/成员类的访问吗?在单元测试下,策略将提供额外的验证,但在发布版本中,它将是简单的直通 .

  • 17

    测试私有方法的最佳方法是通过另一种公共方法 . 如果无法执行此操作,则满足下列条件之一:

    • 私有方法是死代码

    • 您正在测试的课程附近有一种设计气味

    • 您尝试测试的方法不应该是私有的

  • 56

    正如上面提到的那样,一个好方法是通过公共接口测试它们 .

    如果你这样做,最好使用代码覆盖工具(如Emma)来查看你的私有方法是否实际上是从测试中执行的 .

  • 532

    这是我测试私有字段的通用函数:

    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);
    }
    
  • 102

    如果您正在尝试测试您不愿意或无法更改的现有代码,那么反射是一个不错的选择 .

    如果类的设计仍然是灵活的,并且你有一个复杂的私有方法,你想单独测试,我建议你把它拉到一个单独的类,并分别测试该类 . 这不必更改原始类的公共接口;它可以在内部创建辅助类的实例并调用辅助方法 .

    如果您想测试来自辅助方法的困难错误条件,您可以更进一步 . 从helper类中提取一个接口,将一个公共getter和setter添加到原始类中以注入帮助器类(通过其接口使用),然后将一个helper类的模拟版本注入到原始类中以测试原始类的方式响应助手的异常 . 如果您想在不测试帮助程序类的情况下测试原始类,这种方法也很有用 .

  • 53

    我过去曾使用reflection为Java做这个,在我看来这是一个很大的错误 .

    严格来说,您不应该编写直接测试私有方法的单元测试 . 您应该测试的是该类与其他对象的公共 Contract ;你永远不应该直接测试一个对象's internals. If another developer wants to make a small internal change to the class, which doesn' t影响类公共 Contract ,然后他/她必须修改你的基于反射的测试,以确保它的工作原理 . 如果您在整个项目中反复执行此操作,则单元测试将不再是对代码运行状况的有用度量,并开始成为开发的障碍,并对开发团队造成烦扰 .

    我建议改为使用代码覆盖工具(如Cobertura),以确保您编写的单元测试在私有方法中提供良好的代码覆盖率 . 这样,您可以间接测试私有方法正在做什么,并保持更高的敏捷性 .

  • 188

    我最近遇到了这个问题并写了一个名为Picklock的小工具,它避免了显式使用Java反射API的问题,两个例子:

    Calling methods, e.g. private void method(String s) - by Java reflection

    Method method = targetClass.getDeclaredMethod("method", String.class);
    method.setAccessible(true);
    return method.invoke(targetObject, "mystring");
    

    Calling methods, e.g. private void method(String s) - by Picklock

    interface Accessible {
      void method(String s);
    }
    
    ...
    Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
    a.method("mystring");
    

    Setting fields, e.g. private BigInteger amount; - by Java reflection

    Field field = targetClass.getDeclaredField("amount");
    field.setAccessible(true);
    field.set(object, BigInteger.valueOf(42));
    

    Setting fields, e.g. private BigInteger amount; - by Picklock

    interface Accessible {
      void setAmount(BigInteger amount);
    }
    
    ...
    Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
    a.setAmount(BigInteger.valueOf(42));
    
  • 12

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

    这将告诉使用此方法的任何人要小心,即使在包中也不能直接访问它 . 另外,测试类不必位于同一个包 physically 中,而是位于 test 文件夹下的同一个包中 .

    例如,如果要测试的方法在 src/main/java/mypackage/MyClass.java 中,那么您的测试调用应放在 src/test/java/mypackage/MyClassTest.java 中 . 这样,您就可以访问测试类中的测试方法 .

  • 6

    答案来自JUnit.org FAQ page

    但如果必须......如果您使用的是JDK 1.3或更高版本,则可以使用反射来破坏访问控制机制借助PrivilegedAccessor . 有关如何使用它的详细信息,请阅读本文 . 如果您使用的是JDK 1.6或更高版本并使用@Test注释测试,则可以使用Dp4j在测试方法中注入反射 . 有关如何使用它的详细信息,请参阅此测试脚本 .

    附:我是Dp4j的主要撰稿人,如果你需要帮助,请问me . :)

  • 1471

    私有方法由公共方法调用,因此公共方法的输入还应测试由这些公共方法调用的私有方法 . 当公共方法失败时,那可能是私有方法失败 .

  • 31

    在尝试使用Cem Catikkas'solution using reflection for Java之后,我正在寻找使用反射的替代方法,并且可以访问您正在测试的源,这仍然是一个选项 .

    测试类的私有方法可能是有用的,特别是在test-driven development中,您希望在编写任何代码之前设计小型测试 .

    创建可访问私有成员和方法的测试可以测试难以专门定位的代码区域,只能访问公共方法 . 如果公共方法涉及多个步骤,则它可以包含多个私有方法,然后可以单独进行测试 .

    好处:

    • 可以测试更精细的粒度

    缺点:

    • 测试代码必须与源代码驻留在同一文件中,这可能更难以维护

    • 与.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 Tip 106: Static inner classes for fun and profit

  • 9

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

  • 23

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

  • 11

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

    • Decryption routines - 我不想让任何人只为了测试而看到它们,否则任何人都可以使用它们来解密 . 但它们是代码固有的,复杂的,并且需要始终工作(明显的例外是反射,在大多数情况下,甚至可以用于查看私有方法,当 SecurityManager 未配置为阻止此情况时) .

    • Creating an SDK 供社区消费 . 这里公开具有完全不同的含义,因为这是整个世界可能看到的代码(不仅仅是我的应用程序的内部代码) . 我将代码放入私有方法中,如果我不认为这是代码味道,那就像SDK编程的工作方式一样 . 但是我当然还需要测试我的私有方法,而且它们实际上是我的SDK的功能所在 .

    我理解只测试“ Contract ”的想法 . 但是我没有看到人们可以提倡实际上没有测试代码 - 你的里程可能会有所不同 .

    因此,我的权衡涉及使JUnits与反射复杂化,而不是损害我的安全性和SDK .

  • 19

    如果使用Spring,ReflectionTestUtils提供了一些方便的工具,只需要很少的努力就可以帮助你 . 例如,要在私有成员上设置模拟而不必强制添加不需要的公共setter:

    ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);
    
  • 29

    私有方法由公共方法使用 . 否则,它们就是死代码 . 这就是为什么你测试公共方法,断言公共方法的预期结果,从而断言它消耗的私有方法 .

    在对公共方法运行单元测试之前,应通过调试来测试私有方法的测试 .

    它们也可以使用测试驱动开发进行调试,调试单元测试直到满足所有断言 .

    我个人认为使用TDD创建类更好;创建公共方法存根,然后使用 all 预先定义的断言生成单元测试,因此在编码之前确定方法的预期结果 . 这样,您就不会走错路径,使单元测试断言符合结果 . 您的课程非常强大,并且在所有单元测试通过后都符合要求 .

  • 13

    正如其他人所说......不要直接测试私人方法 . 以下是一些想法:

    • 保持所有方法小而集中(易于测试,易于发现错误)

    • 使用代码覆盖工具 . 我喜欢Cobertura(哦,快乐的一天,看起来像是一个新版本!)

    在单元测试上运行代码覆盖率 . 如果您发现方法未经过完全测试,请添加到测试中以获得覆盖率 . 瞄准100%的代码覆盖率,但意识到你可能不会得到它 .

  • 14

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

    在内部我们使用帮助器来获取/设置 privateprivate static 变量以及调用 privateprivate static 方法 . 以下模式将允许您执行与私有方法和字段相关的任何操作 . 当然,你不能通过反射改变 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)让您查看私有方法 . 同样的事情适用于getDeclaredField . 2. setAccessible(true)需要使用privates .

  • 280

    Spring Framework中,您可以使用此方法测试私有方法:

    ReflectionTestUtils.invokeMethod()
    

    例如:

    ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");
    
  • 10

    我倾向于不测试私有方法 . 有疯狂 . 就个人而言,我相信你应该只测试你公开的接口(包括受保护和内部方法) .

  • 21

    测试私有方法会破坏类的封装,因为每次更改内部实现时都会破坏客户端代码(在本例中为测试) .

    所以不要测试私有方法 .

  • 13

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

  • 7

    从这篇文章:Testing Private Methods with JUnit and SuiteRunner(比尔维纳斯),你基本上有4个选择:

    不要测试私有方法 . 给方法包访问权限 . 使用嵌套测试类 . 使用反射 .

相关问题