ChatGPT解决这个技术问题 Extra ChatGPT

使用 mockito 测试私有方法

public class A {

    public void method(boolean b){
          if (b == true)
               method1();
          else
               method2();
    }

    private void method1() {}
    private void method2() {}
}

public class TestA {

    @Test
    public void testMethod() {
      A a = mock(A.class);
      a.method(true);
      //how to test like    verify(a).method1();
    }
}

如何测试私有方法是否被调用,以及如何使用mockito测试私有方法?


K
KeatsPeeks

不可能通过mockito。从他们的wiki

为什么 Mockito 不模拟私有方法?首先,我们对模拟私有方法并不教条。我们只是不关心私有方法,因为从测试私有方法的角度来看并不存在。以下是 Mockito 不模拟私有方法的几个原因:它需要对永远不会防弹的类加载器进行黑客攻击,并且它会更改 api(您必须使用自定义测试运行器、注释类等)。这很容易解决 - 只需将方法的可见性从私有更改为包保护(或受保护)。它需要我花时间实施和维护它。考虑到第 2 点以及它已经在不同的工具(powermock)中实现的事实,这没有任何意义。最后... Mocking private 方法是对OO理解有问题的暗示。在 OO 中,您希望对象(或角色)协作,而不是方法。忘记帕斯卡和程序代码。在对象中思考。


该声明做出了一个致命的假设:> 模拟私有方法暗示了对 OO 的理解有问题。如果我正在测试一个公共方法,并且它调用私有方法,我想模拟私有方法返回。按照上述假设,甚至不需要实现私有方法。对OO的理解不够深入?
@eggmatters 根据 Baeldung 的说法,“模拟技术应该应用于类的外部依赖项,而不是类本身。如果私有方法的模拟对于测试我们的类至关重要,则通常表明设计不佳。”这是一个很酷的主题softwareengineering.stackexchange.com/questions/100959/…
如果您将对象视为要测试的东西,那可能是正确的。但是如果你想测试功能,那么这些功能就是你想要的。功能是用于模块化的,拥有一个模块意味着在验证任何使用它的东西之前首先验证它到一个理智的程度——这简化并提高了整体测试结果的质量。
测试私有方法逻辑的OO“技巧”实际上是创建具有这些私有方法作为公共方法的新类。通过这种方式,您可以对新的更精细的类进行单元测试,测试以前的私有逻辑。复杂的私有方法逻辑确实可以表明您的类有多个职责,并且可能更好地分解为更细化的类。
D
David Newcomb

您无法使用 Mockito 做到这一点,但您可以使用 Powermock 扩展 Mockito 并模拟私有方法。 Powermock 支持 Mockito。 Here 就是一个例子。


我对这个答案感到困惑。这是嘲笑,但标题是测试私有方法
我已经使用 Powermock 来模拟私有方法,但是如何使用 Powermock 测试私有方法。在哪里,我可以传递一些输入并期望该方法的一些输出,然后验证输出?
你不能。你模拟输入输出,你不能测试真正的功能。
@diyoda_作者想验证方法是否被调用,这必须通过模拟来完成。不幸的是,您只能验证模拟。
M
Mindaugas Jaraminas

这是一个小示例,如何使用 powermock

public class Hello {
    private Hello obj;
    private Integer method1(Long id) {
        return id + 10;
    }
} 

要测试 method1 使用代码:

Hello testObj = new Hello();
Integer result = Whitebox.invokeMethod(testObj, "method1", new Long(10L));

要设置私有对象 obj,请使用:

Hello testObj = new Hello();
Hello newObject = new Hello();
Whitebox.setInternalState(testObj, "obj", newObject);

您的链接仅指向 power mock repo @Mindaugas
@Xavier 是的。如果你喜欢在你的项目中使用它,你可以使用它。
精彩的 !!!用这些简单的例子很好地解释了几乎所有内容:) 因为目的只是测试代码而不是所有框架提供的内容:)
请更新这个。 Whitebox 不再是公共 API 的一部分。
R
Rajat

虽然 Mockito 不提供该功能,但您可以使用 Mockito + JUnit ReflectionUtils 类或 Spring ReflectionTestUtils 类获得相同的结果。请参阅以下 here 中的示例,说明如何调用私有方法:

ReflectionTestUtils.invokeMethod(student, "saveOrUpdate", "From Unit test");

使用 ReflectionTestUtils 和 Mockito 的完整示例可以在书 Mockito for Spring 中找到。

官方文档Spring Testing


ReflectionTestUtils.invokeMethod(学生,“saveOrUpdate”,“argument1”,“argument2”,“argument3”); invokeMethod 的最后一个参数使用 Vargs,它可以接受需要传递给私有方法的多个参数。有用。
这个答案应该有更多的赞成票,这是迄今为止测试私有方法的最简单方法。
S
Singh Arun

通过使用反射,可以从测试类调用私有方法。在这种情况下, //test 方法将是这样的 ... public class TestA { @Test public void testMethod() { A a= new A();方法 privateMethod = A.class.getDeclaredMethod("method1", null); privateMethod.setAccessible(true); // 调用私有方法进行测试 privateMethod.invoke(A, null);如果私有方法调用任何其他私有方法,那么我们需要监视对象并存根另一个方法。测试类将像 ... //测试方法将像这样 ... public class TestA { @测试 public void testMethod() { A a= new A();间谍A = spy(a);方法 privateMethod = A.class.getDeclaredMethod("method1", null); privateMethod.setAccessible(true); doReturn("Test").when(spyA, "method2"); // 如果私有方法 2 返回字符串数据 // 调用私有方法进行测试 privateMethod.invoke(spyA , null); } }

**方法是结合反射和窥探对象。 **method1 和 **method2 是私有方法,method1 调用method2。


上述工作完美。谢谢
D
Dawood ibn Kareem

从行为的角度考虑这一点,而不是从有什么方法的角度考虑。如果 b 为真,则名为 method 的方法具有特定的行为。如果 b 为假,它有不同的行为。这意味着您应该为 method 编写两个不同的测试;每个案例一个。因此,您不必拥有三个面向方法的测试(一个用于 method、一个用于 method1、一个用于 method2),而是有两个面向行为的测试。

与此相关(我最近在另一个 SO 线程中提出了这个建议,结果被称为一个四个字母的单词,所以请随意接受这个);我发现选择反映我正在测试的行为的测试名称而不是方法的名称很有帮助。因此,不要将您的测试称为 testMethod()testMethod1()testMethod2() 等。我喜欢 calculatedPriceIsBasePricePlusTax()taxIsExcludedWhenExcludeIsTrue() 之类的名称,这些名称表明我正在测试什么行为;然后在每种测试方法中,仅测试指示的行为。大多数此类行为仅涉及对公共方法的一次调用,但可能涉及对私有方法的多次调用。

希望这可以帮助。


J
Jaco Van Niekerk

您不应该测试私有方法。只需要测试非私有方法,因为无论如何这些都应该调用私有方法。如果您“想要”测试私有方法,则可能表明您需要重新考虑您的设计:

我是否使用了正确的依赖注入?我是否可能需要将私有方法移动到一个单独的类中并进行测试?这些方法必须是私有的吗? ...他们不能默认或受保护吗?

在上面的例子中,这两个被“随机”调用的方法实际上可能需要放在它们自己的一个类中,测试然后注入到上面的类中。


一个有效的点。但是,对方法使用私有修饰符的原因难道不是因为您只是想减少太长和/或重复的代码吗?将其分离为另一个类就像您将这些代码行提升为一等公民,这些代码不会在其他任何地方重用,因为它专门用于划分冗长的代码并防止重复的代码行。如果您要将它与另一个类分开,那感觉不对;你很容易得到班级爆炸。
注意到 supertonsky,我指的是一般情况。我同意在上述情况下它不应该在一个单独的类中。 (不过,对您的评论 +1 - 这是您在提升私人会员方面提出的非常有效的观点)
@supertonsky,我一直无法找到对此问题的满意答复。我可能使用私有成员有几个原因,而且它们通常并不表示代码异味,我将从测试它们中受益匪浅。人们似乎通过说“不要这样做”来忽略这一点。
抱歉,我选择根据“如果您‘想’测试私有方法,这可能表明您需要对您的设计进行否决”投反对票。好的,很公平,但是进行测试的原因之一是,在没有时间重新考虑设计的最后期限内,您正试图安全地实施需要对私有方法。在一个理想的世界中,这种私有方法是否不需要因为设计是完美的而改变?当然,但在一个完美的世界里,但这没有实际意义,因为在一个需要测试的完美世界里,这一切都行得通。 :)
@约翰。点了,你的反对票是有保证的(+1)。也感谢您的评论-我同意您的观点。在这种情况下,我可以看到以下两个选项之一:要么将该方法设为包私有或受保护,并且像往常一样编写单元测试;或者(这是开玩笑和不好的做法)快速编写一个主要方法以确保它仍然有效。但是,我的回答是基于编写新代码并且在您可能无法篡改原始设计时不重构的场景。
A
Abdullah Choudhury

我能够使用反射使用模拟测试内部的私有方法。这是示例,尝试将其命名为有意义

//Service containing the mock method is injected with mockObjects

@InjectMocks
private ServiceContainingPrivateMethod serviceContainingPrivateMethod;

//Using reflection to change accessibility of the private method

Class<?>[] params = new Class<?>[]{PrivateMethodParameterOne.class, PrivateMethodParameterTwo.class};
    Method m = serviceContainingPrivateMethod .getClass().getDeclaredMethod("privateMethod", params);
    //making private method accessible
    m.setAccessible(true); 
    assertNotNull(m.invoke(serviceContainingPrivateMethod, privateMethodParameterOne, privateMethodParameterTwo).equals(null));

R
Reji

我真的不明白你需要测试私有方法。根本问题是您的公共方法具有 void 作为返回类型,因此您无法测试您的公共方法。因此,您被迫测试您的私有方法。我的猜测正确吗??

一些可能的解决方案(AFAIK):

模拟您的私有方法,但您仍然不会“实际”测试您的方法。验证方法中使用的对象的状态。大多数方法要么对输入值进行一些处理并返回输出,要么更改对象的状态。也可以使用测试对象的期望状态。公共类 A { SomeClass classObj = null;公共无效公共方法(){私人方法(); } private void privateMethod(){ classObj = new SomeClass(); } } [在这里,您可以通过检查 classObj 从 null 到非 null 的状态变化来测试私有方法。] 稍微重构您的代码(希望这不是遗留代码)。我编写方法的基础是,一个人应该总是返回一些东西(一个 int/一个布尔值)。返回值可能或可能不会被实现使用,但它肯定会被测试代码使用。公共类 A { 公共 int 方法(布尔 b) { int nReturn = 0; if (b == true) nReturn = method1();否则 nReturn = method2(); } 私有 int 方法 1() {} 私有 int 方法 2() {} }


F
Fan Jin

实际上有一种方法可以使用 Mockito 测试来自私有成员的方法。假设您有这样的课程:

public class A {
    private SomeOtherClass someOtherClass;
    A() {
        someOtherClass = new SomeOtherClass();
    }
    public void method(boolean b){
        if (b == true)
            someOtherClass.method1();
        else
            someOtherClass.method2();
    }

}

public class SomeOtherClass {
    public void method1() {}
    public void method2() {}
}

如果您想测试 a.method 将调用来自 SomeOtherClass 的方法,您可以编写如下内容。

@Test
public void testPrivateMemberMethodCalled() {
    A a = new A();
    SomeOtherClass someOtherClass = Mockito.spy(new SomeOtherClass());
    ReflectionTestUtils.setField( a, "someOtherClass", someOtherClass);
    a.method( true );

    Mockito.verify( someOtherClass, Mockito.times( 1 ) ).method1();
}

ReflectionTestUtils.setField(); 将用您可以窥探的东西存根私人成员。


R
Roland Schneider

将您的测试放在同一个包中,但使用不同的源文件夹(src/main/java 与 src/test/java)并将这些方法设为包私有。 Imo 可测试性比隐私更重要。


唯一合法的原因是测试遗留系统的一部分。如果你开始测试私有/包私有方法,你就会暴露你的对象内部。这样做通常会导致糟糕的可重构代码。更喜欢组合,这样您就可以实现可测试性,并具有面向对象系统的所有优点。
同意 - 这将是首选方式。但是,如果你真的想用 mockito 测试私有方法,这是你唯一的(类型安全的)选项。虽然我的回答有点草率,但我应该指出风险,就像你和其他人所做的那样。
这是我的首选方式。在包私有级别公开对象内部没有错;而单元测试是白盒测试,你需要了解内部进行测试。
m
milensky

在私有方法不是 void 并且返回值用作外部依赖项方法的参数的情况下,您可以模拟依赖项并使用 ArgumentCaptor 来捕获返回值。例如:

ArgumentCaptor<ByteArrayOutputStream> csvOutputCaptor = ArgumentCaptor.forClass(ByteArrayOutputStream.class);
//Do your thing..
verify(this.awsService).uploadFile(csvOutputCaptor.capture());
....
assertEquals(csvOutputCaptor.getValue().toString(), "blabla");

H
Heschoon

以@aravind-yarram 的回答为基础:不可能通过模拟。从他们的wiki

那么测试私有方法的OO方式是什么?具有复杂逻辑的私有方法可能表明您的类违反了单一职责原则,并且应该将一些逻辑移至新类。

实际上,通过将这些私有方法提取到更细化类的公共方法中,您可以对它们进行单元测试,而不会破坏原始类的封装。