您如何对私有方法进行单元测试?


184

我正在研究Java项目。我是单元测试的新手。在Java类中对私有方法进行单元测试的最佳方法是什么?


1
在StackOverflow上检查此问题。提到并讨论了几种技术。单元测试私有方法的最佳方法是什么?
凯龙(Chiron)

34
我一直认为私有方法不需要测试,因为您应该测试可用的方法。一种公共方法。如果您不能破坏公共方法,那么私有方法到底在做什么呢?
钻机2012年

1
公开和私有方法都应进行测试。因此,测试驱动程序通常需要位于要测试的类中。像这样
过度兑换

2
@Rig-+1-您应该能够从公共方法中调用私有方法的所有必需行为-如果不能,则无论如何都不能调用该功能,因此没有必要进行测试。
詹姆斯·安德森

使用@Jailbreak来自Manifold框架的直接访问私有方法。这样,您的测试代码将保持类型安全和可读性。最重要的是,没有任何设计折衷,也没有为了测试而过度曝光的方法和字段。
斯科特

Answers:


239

通常,您不直接对私有方法进行单元测试。由于它们是私有的,因此请考虑将其作为实现细节。没有人会打电话给他们中的一个,并期望它以特定的方式工作。

相反,您应该测试您的公共接口。如果调用您的私有方法的方法按预期工作,则可以假定您的私有方法正常工作。


39
+1 bazillion。如果从未调用私有方法,请不要对其进行单元测试,将其删除!
Steven A. Lowe

246
我不同意。有时,私有方法只是实现细节,但是它仍然足够复杂,需要进行测试以确保其正确运行。公共接口可能提供的抽象水平太高,无法编写直接针对此特定算法的测试。由于共享数据的缘故,将其分解为一个单独的类并不总是可行的。在这种情况下,我想可以测试一个私有方法。
–quant_dev

10
当然要删除它。不删除不使用的功能的唯一可能原因是,如果您认为将来可能需要使用该功能,那么即使删除该功能,也应在提交日志中记录该功能,或者至少将其注释掉,以便人们知道这是他们可以忽略的额外内容。
2011年

38
@quant_dev:有时需要将一个类重构为其他几个类。您的共享数据也可以放入一个单独的类中。说,“上下文”类。然后,您的两个新类可以引用上下文类以获得它们的共享数据。这似乎是不合理的,但是如果您的私有方法足够复杂以至于需要进行单独的测试,则它是一种代码气味,表明您的对象图需要变得更加细粒度。
Phil

43
该答案回答问题。问题是如何测试私有方法,而不是是否应该测试它们。是否应该对私有方法进行单元测试是一个有趣的问题,值得辩论,但这里不是。适当的回答是在问题中添加一条评论,指出测试私有方法可能不是一个好主意,并提供指向一个单独问题的链接,该问题将更深入地解决此问题。
TallGuy 2014年

118

通常,我会避免这样做。如果您的私有方法如此复杂以至于需要单独的单元测试,则通常意味着它应该拥有自己的类。这可能会鼓励您以可重用的方式编写它。然后,您应该测试新类并在旧类中调用它的公共接口。

另一方面,有时将实现细节分解为单独的类会导致具有复杂接口的类,在新老类之间传递大量数据,或者从OOP的角度看可能看起来不错的设计,匹配来自问题域的直觉(例如,为了避免测试私有方法,将定价模型分为两部分不是很直观,并且可能在以后维护/扩展代码时导致问题)。您不想拥有总是一起更改的“孪生类”。

当在封装性和可测试性之间做出选择时,我宁愿选择第二个。拥有正确的代码(即产生正确的输出)比无法正常工作的好的OOP设计更为重要,因为它没有经过充分的测试。在Java中,您可以简单地授予方法“默认”访问权限,并将单元测试放在同一包中。单元测试只是您要开发的程序包的一部分,可以在测试和要测试的代码之间建立依赖关系。这意味着,当您更改实现时,可能需要更改测试,但是没关系-每次更改实现都需要重新测试代码,如果需要修改测试来做到这一点,则只需执行它。

通常,一类可能提供多个接口。有一个用户界面和一个维护者界面。第二个可以公开更多内容,以确保对代码进行适当的测试。它不必是对私有方法的单元测试,例如可以是日志记录。日志记录也会“破坏封装”,但是我们仍然这样做,因为它是如此有用。


9
+1有趣的点。最后,它是关于可维护性,而不是遵守良好OOP的“规则”。
Phil

9
+1我完全同意封装性的可测试性。
理查德

31

私有方法的测试将取决于它们的复杂性;一些单行私有方法并不能真正保证测试的额外努力(这也可以说是公共方法),但是某些私有方法可能与公共方法一样复杂,并且很难通过公共接口进行测试。

我的首选技术是将私有方法包设为私有,这将允许访问同一包中的单元测试,但仍将其封装在所有其他代码中。这将带来直接测试私有方法逻辑的优势,而不必依靠公共方法测试来覆盖(可能)复杂逻辑的所有部分。

如果将其与Google Guava库中的@VisibleForTesting批注配对使用,则可以清楚地将此包私有方法标记为可见,仅用于测试,因此,任何其他类都不应调用该方法。

这种技术的反对者认为,这将破坏封装并打开私有方法以在同一程序包中进行编码。虽然我同意这样做会破坏封装并向其他类开放私有代码,但我认为测试复杂的逻辑比严格封装更重要,并且不使用明确标记为可见仅用于测试的包私有方法,这是开发人员的责任使用和更改代码库。

测试之前的私有方法:

private int add(int a, int b){
    return a + b;
}

包私有方法准备测试:

@VisibleForTesting
int add(int a, int b){
    return a + b;
}

注意:将测试放在同一包中并不等同于将它们放在同一物理文件夹中。通常,将主代码和测试代码分隔为单独的物理文件夹结构是一种好习惯,但是只要将类定义在同一软件包中,该技术就可以使用。


14

如果您不能使用外部API或不想使用外部API,您仍然可以使用纯标准JDK API通过反射来访问私有方法。这是一个例子

MyObject obj = new MyObject();
Method privateMethod = MyObject.class.getDeclaredMethod("getFoo", null);
privateMethod.setAccessible(true);
String returnValue = (String) privateMethod.invoke(obj, null);
System.out.println("returnValue = " + returnValue);

检查Java教程http://docs.oracle.com/javase/tutorial/reflect/或Java API http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summary。 html以获得更多信息。

正如@kij在他的回答中所引用的那样,有时使用反射的简单解决方案确实可以很好地测试私有方法。


9

单元测试用例意味着测试代码单元。这并不意味着测试接口,因为如果您正在测试接口,则并不意味着您正在测试代码单元。这成为一种黑匣子测试。同样,比在接口级别确定问题然后尝试调试哪个模块不起作用,最好在最小的单元级别上查找问题。因此,无论其范围如何,都应对单元测试用例进行测试。以下是测试私有方法的一种方法。

如果使用的是Java,则可以使用提供Deencapsulation.invoke的jmockit来调用正在测试的类的任何私有方法。它最终使用反射来调用它,但是围绕它提供了一个不错的包装器。(https://code.google.com/p/jmockit/


这如何回答所提问题?
t 2014年

@gnat,问题是在Java类中对私有方法进行单元测试的最佳方法是什么?我的第一点回答了这个问题。
agrawalankur 2014年

您的第一点只是广告工具,但并不能解释为什么您认为它比其他工具更好,例如,如最佳答案中所建议并解释的那样,
gnat 2014年

jmockit搬了,旧的官方页面上有一篇关于“合成尿和人造小便”的文章。顺便说一下,Wich仍然与嘲笑的东西有关,但在这里不相关:)新的官方页面是:jmockit.github.io
Guillaume

8

首先,正如其他作者所建议的:如果确实需要测试私有方法,请三思。如果是这样, ...

在.NET中,您可以将其转换为“内部”方法,并使程序包“ InternalVisible ”成为您的单元测试项目。

在Java中,您可以在要测试的类中编写测试本身,并且您的测试方法也应该能够调用私有方法。我实际上并没有丰富的Java经验,所以这可能不是最佳实践。

谢谢。


7

我通常会保护此类方法。假设您的课程位于:

src/main/java/you/Class.java

您可以通过以下方式创建测试类:

src/test/java/you/TestClass.java

现在,您可以访问受保护的方法并可以对其进行单元测试(JUnit或TestNG并不重要),但是您可以将这些方法保留给不需要的调用者。

请注意,这需要一个Maven风格的源树。


3

如果您真的需要测试私有方法,那么使用Java,您可以使用fest assert和/或fest reflect。它使用反射。

使用maven导入库(给定的版本不是我认为的最新版本)或直接将其导入类路径中:

<dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-assert</artifactId>
     <version>1.4</version>
    </dependency>

    <dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-reflect</artifactId>
    <version>1.2</version>
</dependency>

例如,如果您有一个名为“ MyClass”的类,并且带有一个名为“ myPrivateMethod”的私有方法,该方法将一个String作为参数并将其值更新为“ this is cool testing!”,则可以执行以下junit测试:

import static org.fest.reflect.core.Reflection.method;
...

MyClass objectToTest;

@Before
public void setUp(){
   objectToTest = new MyClass();
}

@Test
public void testPrivateMethod(){
   // GIVEN
   String myOriginalString = "toto";
   // WHEN
   method("myPrivateMethod").withParameterTypes(String.class).in(objectToTest).invoke(myOriginalString);
   // THEN
   Assert.assertEquals("this is cool testing !", myOriginalString);
}

该库还使您能够通过模拟替换任何bean属性(无论它们是私有的还是未写入setter),并将其与Mockito或任何其他模拟框架一起使用确实很酷。目前,您唯一需要了解的内容(不知道在下一版本中是否会更好)是您要操作的目标字段/方法的名称及其签名。


2
虽然反射使您可以测试私有方法,但对于检测私有方法的使用却不利。如果您更改私有方法的名称,这将破坏您的测试。
理查德

1
测试会失败,但是从我的角度来看这是一个小问题。我完全同意您在以下有关程序包可见性的解释(对于单独的文件夹中的测试类,但程序包相同),但有时您不这样做真的有选择。例如,如果您在企业中的使命很短,没有应用“好的”方法(测试类不在同一包中,等等),而您又没有时间重构正在使用的现有代码/测试,这仍然是一个不错的选择。
kij 2012年

2

我通常在C#中所做的是使我的方法受保护而不是私有的。它是私有访问修饰符,但是它对所有未从被测类继承的类隐藏了该方法。

public class classUnderTest
{
   //this would normally be private
   protected void methodToTest()
   {
     //some code here
   }
}

任何不直接从classUnderTest继承的类都不知道methodToTest甚至存在。在测试代​​码中,我可以创建一个特殊的测试类,该类可以扩展并提供对该方法的访问...

class TestingClass : classUnderTest
{
   public void methodToTest()
   {
     //this returns the method i would like to test
     return base.methodToTest();
   }
}

此类仅存在于我的测试项目中。其唯一目的是提供对该单一方法的访问。它使我可以访问大多数其他班级没有的地方。


4
protected是公共API的一部分,并受到相同的限制(永远不能更改,必须记录在案,...)。在C#中,您可以internal与一起使用InternalsVisibleTo
CodesInChaos

1

如果将单元测试放在要测试的类的内部类中,则可以轻松地测试私有方法。使用TestNG,单元测试必须是使用@Test注释的公共静态内部类,如下所示:

@Test
public static class UserEditorTest {
    public void test_private_method() {
       assertEquals(new UserEditor().somePrivateMethod(), "success");
    }
}

由于它是一个内部类,因此可以调用private方法。

我的测试是从maven运行的,它会自动找到这些测试用例。如果您只想测试一堂课,可以做

$ mvn test -Dtest=*UserEditorTest

资料来源:http : //www.ninthavenue.com.au/how-to-unit-test-private-methods


4
您是否建议在已部署的程序包的实际代码中包括测试代码(和其他内部类)?

1
测试类是您要测试的类的内部类。如果您不希望部署这些内部类,则可以从包中删除* Test.class文件。
Roger Keays

1
我不喜欢这个选项,但是对于许多人来说,这可能是一个有效的解决方案,不值得不推荐。
比尔K

@RogerKeays:从技术上讲,当您进行部署时,如何删除此类“测试内部类”?请不要说您是手工切割的。
gyorgyabraham 2013年

我不会删除它们。是。我部署的代码永远不会在运行时执行。真的很糟糕。
罗杰·基伊斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.