junit java:测试非公开方法

junit & java : testing non-public methods

本问题已经有最佳答案,请猛点这里访问。

JUnit将只测试我的类中公开的那些方法。我如何对那些不受保护的(即私有的、受保护的)进行JUnit测试?

我可以通过不使用JUnit来测试它们,但我想知道JUnit标准方法是什么。


关于单元测试的一个学派认为,您应该只能测试公共方法,因为您应该只对公共API进行单元测试,通过这样做,您应该在非公共方法中覆盖代码。你的里程数可能会有所不同,我发现有时情况是这样的,有时情况并非如此。

也就是说,有两种方法可以测试非公共方法:

  • 您可以通过将单元测试与它们测试的类放在同一个包中来测试受保护的方法和包范围方法。这是相当普遍的做法。
  • 您可以通过创建一个子类来测试另一个包中的单元测试来测试受保护的方法,该子类重写要作为公共测试的方法,并让这些重写的方法使用super关键字调用原始方法。通常,这个"测试子类"是执行测试的JUnit测试用例类中的一个内部类。在我看来,这有点老土,但我已经做到了。

希望这有帮助。


和许多单元测试问题一样,测试私有方法实际上是一个伪装的设计问题。当我发现自己希望为私有方法编写测试时,我会花一分钟的时间问自己,"我需要如何设计这个,以便通过公共方法彻底测试它?"

如果这不起作用,junitx允许测试私有方法,尽管我相信它只适用于junit3.8。


当你写一个JUnit测试时,你必须做一个微妙的思维转变:"我现在是我自己类的客户了。"这意味着私有是私有的,你只测试客户看到的行为。

如果这个方法真的应该是私有的,我会认为它是一个设计缺陷,为了测试而使它可见。您必须能够根据客户看到的内容推断出它的正确操作。

在我写这篇文章以来的三年里,我开始用Java反射稍微有点不同地接近这个问题。

肮脏的小秘密是,您可以使用反射在JUnit中测试私有方法,就像测试公共方法一样。你可以测试你内心的内容,但不要把它们公开给客户。


最简单的解决方案是将JUnit测试放在同一个包(但目录不同)中,并对方法使用默认(即包私有)可见性。

另一个更复杂的方法是使用反射来访问私有方法。


如果您有大量的逻辑隐藏在相对较少的"公共"入口点下,那么您可能违反了单一责任原则。如果可能,您将希望将代码重构为多个类,最终导致更多"公共"方法从中进行测试。


这里有一个"可能不应该这样做"的方法,每个人都在唠叨你。不过,我认为这当然是在可能性的范围内,有理由这样做。下面的代码将访问私有字段,但私有方法的代码几乎相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void testPrivateField() throws InterruptedException {
    Class<ClassWPrivateField> clazz = ClassWPrivateField.class;
    try {
        Field privateField = clazz.getDeclaredField("nameOfPrivateField");
        privateField.setAccessible(true); // This is the line
        // do stuff
    } catch(NoSuchFieldException nsfe) {
        nsfe.printStackTrace();
        fail();
    } catch(IllegalAccessException iae) {
        iae.printStackTrace();
        fail();
    }

}


在Java项目中,我几乎总是使用Spring,因此,我的对象是为依赖注入而构建的。它们往往是在应用程序上下文中组装的公共接口的相当精细的实现。因此,我很少(如果有)需要测试私有方法,因为类本身足够小,以至于它根本不是问题。

即使我不使用Spring,我也倾向于采用同样的做法,将小的和简单的对象组装成越来越大的抽象,每个抽象相对简单,但被聚合的对象复杂化。

根据我的经验,有必要对私有方法进行单元测试,这是一个指标,表明您正在使用的方法可以(并且应该)简化。

也就是说,如果你仍然觉得有必要:

  • 保护方法可以通过子类进行测试;
  • 包私有方法可以通过将单元测试放在同一包中进行测试;以及
  • 私有方法可以通过提供单元测试,例如,包私有工厂代理方法。不理想,但私密意味着私密。

通常不测试私有方法,因为它们只能(通常)通过另一个公共方法间接进行测试。当您测试驾驶并生成私有方法时,它们通常是"提取方法"重构的结果,并且已经被间接测试过了。

如果你关心用大量的逻辑测试一个私有方法,那么你能做的最聪明的事情就是把代码转移到公共方法中的另一个类中。完成后,使用此代码的前一个方法可以通过让存根或模拟提供功能来简化测试。


我也遇到过同样的问题,"如果它需要私有化,那么它可能需要重构"这句话对我来说并不合适。

假设您有某种功能想要在类内部以某种方式分离出来。例如,假设我有这样的东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class HolderOfSomeStrings{

    private List<String> internal_values;

    public List<String> get()
    {
       List<String> out = new ArrayList<String>();
       for (String s:internal_values)
       {
           out.add(process(s));
       }
      return get;
    }

   private static String process(String input)
   {
     //do something complicated here that other classes shouldn't be interested in
    }

}

这里的要点是,JUnit强制我将进程公开,或者至少受保护,或者将其放入自己的实用程序类中。但是,如果这是HolderOfSomeStrings的某种内部逻辑,那么我根本不清楚这是正确的,在我看来,这应该是私有的,并且在某种程度上使代码更容易被篡改。


借用安迪·亨特的观点,即使你的私人方法也会产生一些你感兴趣的副作用。换句话说,必须从某个公共方法调用它们,并执行一个有趣的任务,使对象的状态发生变化。测试状态变化。

假设您有public方法pubmethod和private方法privemethod。当您调用pubmethod时,它反过来调用privmethod来执行任务(可能是解析字符串)。然后pubMethod使用这个解析的字符串以某种方式设置成员变量的值,或者影响它自己的返回值。通过观察pubMethod的返回值或成员变量(可能通过使用访问器来获取)上所需的效果进行测试。


DP4j Jar

为了测试私有方法,我们需要使用反射和它在所有答案中的指向性。

现在这个任务在dp4j jar的帮助下被简化了。

  • DP4J分析代码并自动为您生成反射API代码。

  • 只需将dp4j.jar添加到类路径。

  • jar包含注释处理器,它们将在代码中查找用@test junit注释注释的方法。

  • DP4J分析这些方法的代码,如果发现非法访问私有方法,它将用Java的反射API替换等效的私有方法引用。

在此处获取更多详细信息


您可以使用testng而不是junit,它不关心方法是私有的还是公共的。


使用上面的反射来测试私有方法。如果我们遵循TDD,我们应该测试私有方法,因为TDD意味着以后不会有任何意外。因此,人们不应该等待完成他的公共方法来测试私密。这有助于在重新因子分解上进行更细粒度的回归测试。


查找"privateaccessor.invoke"。我的代码从"junitx.util"导入它,但我不知道它来自哪里。


我完全同意@duffymo的观点,即应该从客户的角度测试代码(尽管他说他不再这样想)。然而,从这个角度来看,私人与他人有着不同的含义。私有方法的客户机是类本身,所以我更喜欢通过外部(公共/包保护的)API测试它们。但是,受保护和包保护的成员是为外部客户机而存在的,因此我使用继承所属类或驻留在同一包中的伪造来测试它们。


作为这方面的一个分支,我不确定每个人都会对整个"polyglot编程"问题有什么看法,但是groovy测试可以在junit中运行,并且忽略整个公共/非公共问题。旁注,它被正式列为"臭虫",但当他们试图修复它时,有一场暴风雨围绕它,它被放回原来的样子。


与几乎每一篇文章都一致——您可能应该重构,而不应该测试私有的,除非通过公共的,只想添加一种不同的思考方式……

把类本身看作一个"单元",而不是一个方法。您正在测试该类,它可以保持有效状态,而不管如何调用它的公共方法。

调用私有方法会破坏封装,实际上会使测试失效。