繁体   English   中英

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

[英]How do I test a class that has private methods, fields or inner classes?

如何使用 JUnit 测试具有内部私有方法、字段或嵌套类的类?

仅仅为了能够运行测试而更改方法的访问修饰符似乎很糟糕。

更新:

大约 10 年后,测试私有方法或任何无法访问的成员的最佳方法可能是通过来自Manifold框架的@Jailbreak

 @Jailbreak Foo foo = new Foo(); // Direct, *type-safe* access to *all* foo's members foo.privateMethod(x, y, z); foo.privateField = value;

这样你的代码就保持了类型安全和可读性。 没有设计妥协,没有为了测试而过度曝光方法和领域。

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

在内部,我们使用助手来获取/设置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)可让您查看private方法。 同样的事情适用于getDeclaredField
2. setAccessible(true)需要玩弄私有。

测试私有方法的最佳方法是通过另一个公共方法。 如果无法做到这一点,则以下条件之一为真:

  1. 私有方法是死代码
  2. 您正在测试的课程附近有一种设计气味
  3. 您尝试测试的方法不应该是私有的

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

我通常解决这些问题的方法是梳理出一个包含有趣部分的新类。 通常,这个方法和它交互的字段,也许另外一两个方法可以被提取到一个新的类中。

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

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

过去我曾使用反射为 Java 做这件事,在我看来这是一个很大的错误。

严格来说,您应该编写直接测试私有方法的单元测试。 应该测试的是该类与其他对象之间的公共契约; 您永远不应该直接测试对象的内部结构。 如果另一个开发人员想要对类进行小的内部更改,这不会影响类公共契约,那么他/她必须修改基于反射的测试以确保它有效。 如果您在整个项目中重复执行此操作,那么单元测试将不再是衡量代码健康状况的有用指标,而会开始成为开发的障碍,并使开发团队感到烦恼。

相反,我建议您使用 Cobertura 等代码覆盖工具,以确保您编写的单元测试在私有方法中提供对代码的良好覆盖。 这样,您可以间接测试私有方法在做什么,并保持更高级别的敏捷性。

从这篇文章:使用 JUnit 和 SuiteRunner (Bill Venners)测试私有方法,您基本上有 4 个选项:

  1. 不要测试私有方法。
  2. 授予方法包访问权限。
  3. 使用嵌套测试类。
  4. 使用反射。

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

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

  1. 解密例程- 我不想让任何人看到它们只是为了测试,否则任何人都可以使用它们来解密。 但它们是代码固有的、复杂的,并且需要始终工作(明显的例外是反射,在大多数情况下,当SecurityManager未配置为防止这种情况发生时,它甚至可以用于查看私有方法)。
  2. 创建社区消费SDK 这里 public 具有完全不同的含义,因为这是全世界都可以看到的代码(不仅仅是我的应用程序内部的)。 如果我不想让 SDK 用户看到它,我会将代码放入私有方法中 - 我不认为这是代码异味,只是作为 SDK 编程的工作方式。 但是当然我仍然需要测试我的私有方法,它们是我的 SDK 功能实际存在的地方。

我理解只测试“合同”的想法。 但是我认为没有人可以提倡实际上不测试代码 - 您的里程可能会有所不同。

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

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

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

ReflectionTestUtils.invokeMethod()

例如:

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

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

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

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

与大型和古怪的类测试的遗留代码,它往往是能够测试一个私人(或公共)方法我正在写,现在非常有帮助。

我使用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);

使用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

另请参阅: Java 技巧 106:为了乐趣和利润而使用的静态内部类

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

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

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

我个人认为使用 TDD 创建类会更好; 创建公共方法存根,然后使用预先定义的所有断言生成单元测试,因此在编码之前确定方法的预期结果。 这样,您就不会走错路,使单元测试断言适合结果。 当您的所有单元测试通过时,您的类就会变得健壮并满足要求。

正如其他人所说......不要直接测试私有方法。 这里有一些想法:

  1. 保持所有方法小而集中(易于测试,易于发现问题所在)
  2. 使用代码覆盖工具。 我喜欢Cobertura (哦,快乐的一天,看起来新版本已经出来了!)

在单元测试上运行代码覆盖率。 如果您看到方法未经过完全测试,请添加到测试中以提高覆盖率。 目标是 100% 的代码覆盖率,但要意识到您可能不会得到它。

如果使用 Spring, ReflectionTestUtils提供了一些方便的工具,可以以最少的努力在这里提供帮助。 例如,要在私有成员上设置模拟而不被迫添加不需要的公共设置器:

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

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

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

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

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

所以不要测试私有方法。

JUnit.org 常见问题页面的答案:

但如果你必须...

如果您使用的是 JDK 1.3 或更高版本,则可以借助PrivilegedAccessor使用反射来颠覆访问控制机制。 有关如何使用它的详细信息,请阅读 这篇文章

如果您使用的是 JDK 1.6 或更高版本,并且您使用 @Test 注释您的测试,您可以使用Dp4j在您的测试方法中注入反射。 有关如何使用它的详细信息,请参阅此测试脚本

PS我的主要贡献者Dp4j ,问是否需要帮助。 :)

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

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

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

我建议你重构一下你的代码。 当你不得不开始考虑使用反射或其他类型的东西来测试你的代码时,你的代码出了问题。

你提到了不同类型的问题。 让我们从私有字段开始。 如果是私有字段,我会添加一个新的构造函数并将字段注入其中。 取而代之的是:

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

大多数我写的看起来像是依赖注入模式。 以我个人的经验,它在测试时非常有用,我认为这种代码更清晰,更易于维护。 对于嵌套类,我也会这么说。 如果嵌套类包含大量逻辑,最好将其作为包私有类移动并将其注入需要它的类中。

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

还有 setter 注入,但我不建议使用它。 我最好坚持使用构造函数并在真正需要时初始化所有内容,从而留下注入必要依赖项的可能性。

请参阅下面的示例;

应添加以下导入语句:

import org.powermock.reflect.Whitebox;

现在您可以直接传递具有私有方法、要调用的方法名称和附加参数的对象,如下所示。

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

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

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

私有方法只能在同一个类中访问。 所以没有办法从任何测试类中测试目标类的“私有”方法。 一个出路是您可以手动执行单元测试,或者可以将您的方法从“私有”更改为“受保护”。

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

如果以上都不能满足您的要求,请使用反射方式访问私有方法。

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);

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

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

https://github.com/stephanenicolas/boundbox

正如上面许多人所建议的,一个好方法是通过您的公共接口测试它们。

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

首先,我会抛出这个问题:为什么你的私人成员需要隔离测试? 他们有那么复杂,提供如此复杂的行为,以至于需要远离公众表面进行测试吗? 这是单元测试,而不是“代码行”测试。 不要为小事出汗。

如果它们那么大,足够大,以至于这些私有成员都是一个复杂度很大的“单元”——考虑从这个类中重构这些私有成员。

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

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

调用方法,例如private void method(String s) - 通过 Java 反射

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

调用方法,例如private void method(String s) - 通过 Picklock

interface Accessible {
  void method(String s);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

设置字段,例如private BigInteger amount; - 通过 Java 反射

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

设置字段,例如private BigInteger amount; - 皮克洛克

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));

对于Java,我将使用Reflection ,因为我不喜欢仅出于测试目的而更改对声明的方法的包的访问权限的想法。 但是,我通常只测试公共方法,这也应确保私有方法正常工作。

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

这不是真的。 正如Cem Catikkas的回答中所述,您当然可以。

在 C++ 中:在包含具有要测试的私有函数的类头之前

使用此代码:

#define private public
#define protected public

您可以关闭 Java 反射的访问限制,这样私有就没有任何意义。

setAccessible(true)调用就是这样做的。

唯一的限制是 ClassLoader 可能不允许您这样做。

有关在 Java 中执行此操作的方法,请参阅Subverting Java Access Protection for Unit Testing (Ross Burton)。

我想贡献那些担心没有像许多帖子建议的那样测试私有方法的人,请考虑代码覆盖率工具将准确确定测试了多少代码以及泄漏的位置,因此可以量化地接受做这个。

我要说一些需要说但很多人可能不喜欢听的话。 你们中那些将问题的作者引向“变通”的答案的人正在对社区造成巨大的伤害。 测试是所有工程学科的重要组成部分,您不会想要购买未经适当测试和以有意义的方式测试的汽车,那么为什么有人想要购买或使用测试不佳的软件呢? 人们无论如何都这样做的原因可能是因为经过严重测试的软件的影响是事后感受到的,我们通常不会将它们与身体伤害联系起来。 这是一种非常危险的观念,很难改变,但我们有责任提供安全的产品,不管管理层欺负我们做什么。 想想 Equifax 黑客...

我们必须努力建立一个鼓励良好的软件工程实践的环境,这并不意味着排斥我们中间那些不认真对待他们的技术的弱者/懒惰者,而是创造一种问责和自我反思的现状,鼓励每个人追求成长,无论是精神上还是技巧上。 我仍在学习,我自己可能有错误的看法/意见,但我坚信我们需要对良好实践负责,避免不负责任的黑客攻击或解决问题的方法。

使用 ExpectedException 时对@Cem Catikka 的评论的快速补充:

请记住,您预期的异常将包含在 InvocationTargetException 中,因此为了获得您的异常,您必须抛出您收到的 InvocationTargetException 的原因。 类似于(在 BizService 上测试私有方法 validateRequest() ):

@Rule
public ExpectedException thrown = ExpectedException.none();

@Autowired(required = true)
private BizService svc;


@Test
public void testValidateRequest() throws Exception {

    thrown.expect(BizException.class);
    thrown.expectMessage(expectMessage);

    BizRequest request = /* Mock it, read from source - file, etc. */;
    validateRequest(request);
}

private void validateRequest(BizRequest request) throws Exception {
    Method method = svc.getClass().getDeclaredMethod("validateRequest", BizRequest.class);
    method.setAccessible(true);
    try {
        method.invoke(svc, request);
    }
    catch (InvocationTargetException e) {
        throw ((BizException)e.getCause());
    }
 }

如果您在spring ,请使用此实用程序类。

ReflectionTestUtils.invokeMethod(new ClassName(), "privateMethodName");

使用 lombok 详细说明我的示例,如下所示。 私有字段,私有方法:

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

我想分享一个关于测试的规则,它特别与这个主题相关:

我认为你永远不应该为了更轻松地编写测试而调整生产代码。

其他帖子中有一些建议可以调整原始类以测试私有方法。 如果您将方法/字段的可访问性更改为包私有或受保护,只是为了让测试可以访问它,那么您就违背了私有访问指令存在的目的。

如果我们想要测试驱动的开发,为什么还要有私有字段/方法/类? 我们是否应该将所有内容都声明为私有包,甚至是公开包,以便我们可以毫不费力地进行测试? - 我不这么认为。

从另一个角度来看。 测试不应加重生产应用程序的性能和执行。 如果您只是为了更容易测试而更改生产代码,那么您可能会以某种方式加重性能和应用程序的执行。 如果您开始将私有访问权限更改为包私有,您最终可能会想出关于向原始类添加更多代码的“巧妙想法”,这会给可读性带来额外的干扰,并可能加重应用程序的性能。

另一方面,通过将私有访问更改为一些限制较少的访问,您为开发人员在应用程序的未来开发中滥用新情况打开了可能性。 你不是强迫他以正确的方式发展,而是为他打开了新的可能性,让他有能力在未来做出错误的选择。

当然,这条规则可能有一些例外,但要清楚地了解什么是规则,什么是例外,以及为什么要这样做。

我不确定这是否是一种好技术,但我开发了以下模式来对私有方法进行单元测试:

我不会修改要测试的方法的可见性并添加其他方法。 相反,我为我想要测试的每个私有方法添加了一个额外的公共方法。 我将此附加方法称为Test-Port并用前缀t_表示它们。 这个Test-Port方法然后简单地访问相应的私有方法。

此外,我向Test-Port方法添加了一个布尔标志,以决定是否从外部通过Test-Port方法授予对私有方法的访问权限。 这个标志然后在一个静态类中全局设置,我在其中放置了应用程序的其他全局设置。 所以我可以在一个地方打开和关闭对私有方法的访问,例如在相应的单元测试中。

如果您的测试类与应该测试的类在同一个包中怎么办?

但是当然在不同的目录中,源代码的src & classestest/src classestest/srctest/classes classes。 classestest/classes在你的类路径中。

从测试框架测试 Java 私有方法的最佳和正确的合法方法是方法上的@VisibleForTesting注释,因此相同的方法对于测试框架将像公共方法一样可见。

在 C# 中,您可以使用System.Reflection ,但在 Java 中我不知道。 虽然我觉得无论如何都要回答这个问题,因为如果你“觉得你需要对私有方法进行单元测试”,我的猜测是还有其他事情是错误的......

我会认真考虑以全新的眼光再次审视我的建筑......

PowerMock.Whitebox是我见过的最好的选择,但是当我阅读它的源代码时,它会通过反射读取私有字段,所以我想我有我的答案:

  • 使用 PowerMock 测试私有内部状态(字段),或者只是反射而不引入另一个独立性的开销
  • 对于私有方法:实际上,对这个问题本身的点赞,以及大量的评论和答案,表明这是一个非常并发和有争议的话题,无法给出适合所有情况的明确答案 我知道只应测试合同,但我们也需要考虑覆盖范围。 实际上,我怀疑只有测试合同才能 100% 使类免于错误 私有方法是在定义数据的类中处理数据的方法,因此其他类不感兴趣,因此我们不能简单地公开以使其可测试。 我会尽量不去测试它们,但是当你不得不去测试的时候就去试试吧,忘记这里的所有答案 您比互联网上的任何其他人都更了解自己的情况和限制。 当您可以控制代码时,请使用它。 有考虑,但没有过度思考。

一段时间后,当我重新考虑它时,我仍然相信这是真的,但我看到了更好的方法。

首先, Powermock.Whitebox仍然可用。

而且,Mockito Whitebox在 v2 之后被隐藏了(我可以在Whitebox找到的最新版本是testImplementation 'org.mockito:mockito-core:1.10.19' )并且它一直是org.mockito.internal包的一部分,它是将来容易发生重大变化(请参阅此帖子)。 所以现在我倾向于不使用它。

在 Gradle/Maven 项目中,如果您定义私有方法或字段,则没有其他方法可以通过反射来访问它们,因此第一部分仍然成立。 但是,如果您将可见性更改为“包私有”,则测试包中遵循相同结构的test将可以访问它们。 这也是鼓励我们在maintest包中创建相同层次结构的另一个重要原因。 因此,当您可以控制生产代码和测试时,删除该private访问修饰符可能是您的最佳选择,因为相对而言它不会造成巨大影响。 而且,这使得测试和私有方法监视成为可能。

@Autowired
private SomeService service; // with a package private method "doSomething()"

@Test
void shouldReturnTrueDoSomething() {
    assertThat(doSomething(input), is(true)); // package private method testing
}

@Test
void shouldReturnTrueWhenServiceThrowsException() {
    SomeService spy = Mockito.spy(service); // spying real object
    doThrow(new AppException()).when(spy).doSomething(input); // spy package private method
    ...

}

当涉及到内部字段时,在 Spring 中你有ReflectionUtils.setField()

最后,有时我们可以绕过问题本身:如果有覆盖要求要满足,也许您可​​以将这些私有方法移动到内部静态类中,而在 Jacoco 中忽略该类。 我刚刚找到了一些方法来忽略 Jacoco gradle 任务中的内部类。 另一个问题

我认为大多数答案都太粗糙了。 如果您应该测试私有方法,这取决于您的代码。

过去我使用私有方法测试,我通过反射来做到这一点。 这个对我有用。 我意识到它的问题,但对我来说这是最好的解决方案。

我有一个繁重的应用程序,可以在超过一百万人的人口中模拟人类行为。 每个人都由一个对象表示。 该应用程序的主要目的是跟踪某物(疾病、信息、思想)如何在人群中传播。

为此,我有将疾病或信息从一个对象传递到另一个对象的方法。 绝对没有理由公开这些方法,因为最终用户对人与人之间的一次传递不感兴趣。 最终用户只对它如何在人群中传播的宏图感兴趣。 所以这些方法是私有的。

但是我想确定地知道将一点信息从一个人传递给另一个人的单一行为是否正在做它应该做的事情。 通过公共用户界面对其进行测试也是不可能的,因为它只是不公开的,而且我认为仅出于测试目的而将其公开是很尴尬的。 应用程序的最终输出由一直执行的数亿个此类单个步骤定义。 我也无法测试最终输出,因为这涉及复杂的随机稳定性,这使得测试变得不可预测。

因此,我测试应用程序的方法是测试将信息从一个人传递给另一个人的单个步骤。 这些都是私有方法。 所以我使用反射来测试它。

我讲这个故事是为了表明它不是那么简单的黑白故事。 这取决于您的应用程序什么是最好的。 在某些情况下,通过反射测试私有方法可能是您的最佳选择。

如果这里的某些人在我的用例中知道更好的解决方案,我当然很乐意接受纠正......

JML 有一个 spec_public 注释注释语法,允许您在测试期间将方法指定为 public:

private /*@ spec_public @*/ int methodName(){
...
}

此语法在http://www.eecs.ucf.edu/~leavens/JML/jmlrefman/jmlrefman_2.html#SEC12 中讨论。 还有一个程序可以将 JML 规范转换为 JUnit 测试。 我不确定它的效果如何或它的功能是什么,但它似乎没有必要,因为 JML 本身就是一个可行的测试框架。

您可以使用 PowerMockito 为要测试的私有方法中调用/使用的私有字段和私有方法设置返回值:

例如。 设置私有方法的返回值:

MyClient classUnderTest = PowerMockito.spy(new MyClient());

//Set expected return value
PowerMockito.doReturn(20).when(classUnderTest, "myPrivateMethod", anyString(), anyInt());
//This is very important otherwise it will not work
classUnderTest.myPrivateMethod(); 

//Setting private field value as someValue:
Whitebox.setInternalState(classUnderTest, "privateField", someValue);

最后,您可以通过以下方式验证您的被测私有方法是否根据上述设置值返回正确值:

String msg = Whitebox.invokeMethod(obj, "privateMethodToBeTested", "param1");
Assert.assertEquals(privateMsg, msg);

或者

如果 classUnderTest 私有方法没有返回值但它设置了另一个私有字段,那么您可以获取该私有字段值以查看它是否设置正确:

//To get value of private field
MyClass obj = Whitebox.getInternalState(classUnderTest, "foo");
assertThat(obj, is(notNull(MyClass.class))); // or test value

我和我的团队正在使用 Typemock,它有一个允许你伪造非公共方法的 API 最近他们增加了伪造不可见类型和使用 XUnit 的能力。

如果您确实需要直接测试私有方法/类等,则可以使用其他答案中已经提到的反射。 但是,如果涉及到这一点,我宁愿使用框架提供的 util 类,而不是直接处理反射。 例如,对于 Java,我们有:

至于如何使用它们,您可以在网上找到大量文章。 这是我特别喜欢的一个:

您可以创建一个特殊的公共方法来代理要测试的私有方法。 使用 IntelliJ 时,@TestOnly 注释是开箱即用的。 缺点是如果有人想在公共上下文中使用私有方法,他可以做到。 但是他会被注解和方法名警告。 在 IntelliJ 上执行此操作时会出现警告。

import org.jetbrains.annotations.TestOnly

class MyClass {

    private void aPrivateMethod() {}

    @TestOnly
    public void aPrivateMethodForTest() {
        aPrivateMethod()
    }
}

Android 有来自android.support.annotation包的@VisibleForTesting注释。

@VisibleForTesting批注表明带批注的方法比使方法可测试所需的通常更可见。 这个注解有一个可选的otherwise参数,它允许你指定方法的可见性应该是什么,如果不是为了测试可见的需要。 林特使用otherwise参数来执行预定的知名度。

在实践中,这意味着您应该打开一个方法进行测试, @VisibleForTesting注释将显示警告。

例如

package com.mypackage;

public class ClassA {

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    static void myMethod() {

    }
}

当您在同一个包 (com.mypackage) 中调用 ClassA.myMethod() 时,您将看到警告。

在此处输入图片说明

对于 C++(C++11 起),将测试类添加为朋友可以完美地工作并且不会破坏生产封装。

假设我们有一些类Foo带有一些真正需要测试的私有函数,还有一些类FooTest应该可以访问 Foo 的私有成员。 那么我们应该写如下:

// prod.h: some production code header

// forward declaration is enough
// we should not include testing headers into production code
class FooTest;

class Foo
{
  // that does not affect Foo's functionality
  // but now we have access to Foo's members from FooTest
  friend FooTest;
public:
  Foo();
private:
  bool veryComplicatedPrivateFuncThatReallyRequiresTesting();
}
// test.cpp: some test
#include <prod.h>

class FooTest
{
public:
  void complicatedFisture() {
    Foo foo;
    ASSERT_TRUE(foo.veryComplicatedPrivateFuncThatReallyRequiresTesting());
  }
}

int main(int /*argc*/, char* argv[])
{
  FooTest test;
  test.complicatedFixture();  // and it really works!
}

我只测试公共接口,但我知道我会保护特定的私有方法,因此我可以完全模拟它们,或者添加特定于单元测试目的的附加步骤。 一般情况是挂接我可以从单元测试中设置的标志,以使某些方法有意引起异常,以便能够测试故障路径; 异常触发代码仅在受保护方法的重写实现中的测试路径中。

不过,我尽量减少对此的需求,并且我总是记录确切的原因以避免混淆。

下面的Reflection TestUtil用于测试私有方法的原子性

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)

如果您只使用 Mockito:

您可以将私有方法视为正在测试的公共方法的一部分。 在测试公共方法时,您可以确保在私有方法中涵盖所有情况。

假设您是仅使用 mockito 的用户(您不被允许或不想使用 Powermock 或反射或任何此类工具)并且您不想更改正在测试的现有代码或库,这可能是最好的方法。

如果您选择这种方式,您唯一需要处理的是在私有方法中本地声明的变量(用户定义的对象)。 如果私有方法依赖于本地声明的变量对象及其方法,请确保将这些用户定义的对象全局声明为私有对象,而不是本地声明的对象。 您可以在本地实例化这些对象。

这允许您模拟这些对象并将它们注入回测试对象。 您还可以模拟(使用 when/then)他们的方法。

这将允许您在测试公共方法时测试私有方法而不会出错。

优点 1. 代码覆盖 2. 能够测试完整的私有方法。

缺点 1. 对象的范围 - 如果您不希望对象暴露给同一个类中的其他方法,这可能不是您的方式。 2. 当在不同的公共方法和/或在同一方法中多次调用时,您可能会多次测试私有方法。

在你的课堂上:

namespace my_namespace {
    #ifdef UNIT_TEST
        class test_class;
    #endif

    class my_class {
        public:
            #ifdef UNIT_TEST
                friend class test_class;
            #endif
        private:
            void fun() { cout << "I am private" << endl; }
    }
}

在您的单元测试类中:

#ifndef UNIT_TEST
    #define UNIT_TEST
#endif

#include "my_class.h"

class my_namespace::test_class {
    public:
        void fun() { my_obj.fun(); }
    private:
        my_class my_obj;
}

void my_unit_test() {
    test_class test_obj;
    test_obj.fun(); // here you accessed the private function ;)
}

我感觉完全一样...更改方法的访问修饰符只是为了能够运行测试,这对我来说是个坏主意。 同样在我们公司,我们对此进行了很多讨论,在我看来,测试私有方法的真正好方法是使用 java 反射或其他使方法可测试的框架。 对于复杂的私有方法,我多次这样做,这有助于我保持测试的小型化、可读性和可维护性。

在我在这里阅读了所有答案之后,我不同意那些说“如果你需要测试私有方法,那么就会有代码味道”甚至“不要测试私有方法”的人......所以我有一个给你的小例子:

想象一下,我有一个带有一个公共方法和几个私有方法的类:

public class ConwaysGameOfLife {

    private boolean[][] generationData = new boolean[128][128];

    /**
     * Compute the next generation and return the new state
     * Also saving the new state in generationData
     */
    public boolean[][] computeNextGeneration() {
        boolean[][] tempData = new boolean[128][128];

        for (int yPos=0; yPos<=generationData.length; yPos++) {
            for (int xPos=0; xPos<=generationData[yPos].length; xPos++) {
                int neighbors = countNeighbors(yPos, xPos);
                tempData[yPos][xPos] = determineCellState(neighbors, yPos, xPos);
            }
        }

        generationData = tempData;
        return generationData;
    }

    /**
     * Counting the neighbors for a cell on given position considering all the edge cases
     *
     * @return the amount of found neighbors for a cell
     */
    private int countNeighbors(int yPos, int xPos) {}

    /**
     * Determine the cell state depending on the amount of neighbors of a cell and on a current state of the cell
     *
     * @return the new cell state
     */
    private boolean determineCellState(int neighborsAmount, int yPos, int xPos) {}
}

因此,至少对于“countNeighbors”方法,我需要测试 8 个边缘情况以及一些一般情况(单元格直接位于角落,单元格直接位于矩阵边缘,单元格位于中间某处)。 因此,如果我只是试图通过“computeNextGeneration”方法覆盖所有情况,并且在重构之后,一些测试是红色的,那么确定错误所在的位置可能很耗时。 如果我分别测试“determineCellState”和“countNeighbors”并且在重构和优化之后,“computeNextGeneration”和“determineCellState”的测试是红色的,我很确定错误将出现在“determineCellState”方法中。

此外,如果您从一开始就为这些方法编写单元测试,这些测试将帮助您开发方法/算法,而无需考虑并将其他方法调用和案例包装在公共方法中。 您可以在该方法中编写涵盖您的案例的快速小型测试......例如,如果名称为“countNeighbors_should_return_right_amount_of_neighbors_for_the_right_top_corner_cell()”的测试失败,那么很清楚在哪里寻找错误

Groovy 有一个bug/feature ,通过它您可以调用私有方法,就像它们是公共的一样。 因此,如果您能够在项目中使用 Groovy,那么您可以使用它来代替反射。 查看此页面以获取示例。

还有另一种方法来测试您的私有方法。 如果您在运行配置中“启用断言”,那么您可以在方法本身内部对您的方法进行单元测试。 例如;

assert ("Ercan".equals(person1.name));
assert (Person.count == 2);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM