使用Mockito测试Private方法


104
公开课A {

    公共无效方法(布尔b){
          如果(b == true)
               method1();
          其他
               method2();
    }

    私有void method1(){}
    私有void method2(){}
}
公共课程TestA {

    @测试
    公共无效testMethod(){
      一个a =模拟(A.class);
      a.method(true);
      //如何像verify(a).method1();一样进行测试;
    }
}

如何调用私有方法,以及如何使用Mockito测试私有方法???


Answers:


81

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


19
我对此答案感到困惑。这是在嘲笑,但是标题正在测试私有方法
diyoda_

我已经使用Powermock模拟私有方法,但是如何使用Powermock测试私有方法。在哪里,我可以传递一些输入,并期望该方法产生一些输出,然后验证输出?
Rito

您不能。您模拟输入输出,您不能测试真正的功能。
塔拉

131

无法通过模仿。从他们的维基

为什么Mockito不模拟私有方法?

首先,我们并不是在嘲笑私有方法。我们只是不关心私有方法,因为从测试私有方法的角度来看不存在。Mockito不模拟私有方法的原因有两个:

它需要对类加载器进行黑客攻击,而这些子加载器从来都不是防弹的,它会更改api(您必须使用自定义测试运行器,对类进行注释等)。

解决起来非常容易-只需将方法的可见性从私有更改为受包保护(或受保护)即可。

这需要我花时间实施和维护它。考虑到第二点,并且已经在不同的工具(powermock)中实现了,这是没有意义的。

最后...模拟私有方法暗示对OO理解有问题。在OO中,您希望对象(或角色)进行协作,而不是方法。忘记Pascal和程序代码。思考对象。


1
该语句有一个致命的假设:>嘲笑私有方法暗示着对OO的理解有问题。如果我正在测试一个公共方法,并且它调用了私有方法,那么我想模拟私有方法的返回。按照以上假设,甚至不需要实现私有方法。对OO的理解如何?
eggmatters

1
@eggmatters根据Baeldung的说法,“应该将模拟技术应用于类的外部依赖关系,而不是应用于类本身。如果模拟私有方法对于测试我们的类至关重要,则通常表明设计不良。” 下面是关于它的酷线程softwareengineering.stackexchange.com/questions/100959/...
贾森Glez

34

这是一个如何使用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);

您的链接仅指向电源模拟回购@Mindaugas
Xavier

@ Xavier是的。您可以在项目中使用它。
Mindaugas Jaraminas

1
很棒!!! 用这些简单的示例很好地解释了几乎所有内容:)因为目的只是测试代码,而不是所有框架提供的内容:)
siddhusingh

请更新此。Whitebox不再是公共API的一部分。
user447607 '18

17

从行为的角度考虑此问题,而不是从现有的方法角度考虑。method如果b为true,则调用的方法具有特定的行为。如果b为假,则具有不同的行为。这意味着您应该为编写两个不同的测试method;每种情况一个。因此,您不必进行三个面向方法的测试(一个用于method,一个用于method1,一个用于method2,用于测试,而是进行两个面向行为的测试。

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

希望这可以帮助。


13

尽管Mockito不提供该功能,但您可以使用Mockito + JUnit ReflectionUtils类或Spring ReflectionTestUtils类来获得相同的结果。请参见下面的示例,该示例从此处获取解释如何调用私有方法的示例:

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

可以在《Mockito for Spring》一书中找到ReflectionTestUtils和Mockito的完整示例。


ReflectionTestUtils.invokeMethod(学生,“ saveOrUpdate”,“ argument1”,“ argument2”,“ argument3”));invokeMethod的最后一个参数使用Vargs,Vargs可以采用多个需要传递给私有方法的参数。有用。
蒂姆(Tim)

这个答案应该有更多的投票权,这是迄今为止测试私有方法的最简单方法。
马克斯

9

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

我在使用适当的依赖注入吗?我是否可能需要将私有方法移到单独的类中并进行测试?这些方法必须私有吗?...它们不是默认值还是受保护的?

在上面的实例中,实际上可能需要将被称为“随机”的两个方法放置在它们自己的类中,进行测试,然后将其注入上述类中。


26
一个有效点。但是,难道不是对方法使用private修饰符的原因是因为您只是想砍掉太长和/或重复的代码?将其分隔为另一个类,就像您正在将这些代码行提升为头等公民,因为这些代码行专门用于划分冗长的代码并防止重复的代码行,因此它们将不会在其他任何地方重用。如果您打算将其分离到另一个类,那就感觉不对劲。您将很容易得到全班爆炸。
supertonsky

2
注意到supertonsky,我指的是一般情况。我同意在上述情况下不应将其放在单独的类中。(不过,您的评论为+1-这是您提升私人成员的一个非常正确的观点)
Jaco Van Niekerk

4
@supertonsky,对于此问题,我一直找不到满意的答案。我之所以可以使用私人成员有几个原因,而私人成员经常没有指出代码的味道,因此我将通过对其进行测试而大大受益。人们似乎通过说“只是不做”来避免这种情况。
LuddyPants 2014年

3
抱歉,我选择基于“如果您'想'测试私有方法,则可能表明您需要对您的设计进行投票”来拒绝投票。好的,很公平,但是完全要进行测试的原因之一是,在没有足够时间重新考虑设计的最后期限内,您正在尝试安全地实施需要对设计进行的更改。私有方法。在理想的世界中,是否会因为设计完美而无需更改私有方法?当然可以,但是在一个完美的世界中,这是有争议的,因为在一个需要测试的完美世界中,这一切都是可行的。:)
John Lockwood 2014年

2
@约翰。计分,您的投票同意(+1)。也感谢您的评论-我同意您的观点。在这种情况下,我可以看到以下两种选择之一:将方法设为包私有或受保护,并照常编写单元测试。或(这是嘲讽和不道德的做法)会迅速编写一种主要方法以确保其仍然有效。但是,我的响应是基于以下情况:编写新代码并且在您可能无法篡改原始设计时不进行重构。
Jaco Van Niekerk 2014年

6

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

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

6
  1. 通过使用反射,可以从测试类中调用私有方法。在这种情况下,

    // test方法将像这样...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        // invoke the private method for test
        privateMethod.invoke(A, null);
    
        }
    }
  2. 如果私有方法调用任何其他私有方法,那么我们需要监视对象并存根另一个方法。测试类将类似于...

    // test方法将像这样...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        A spyA = spy(a);
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        doReturn("Test").when(spyA, "method2"); // if private method2 is returning string data
        // invoke the private method for test
        privateMethod.invoke(spyA , null);
    
        }
    }

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


4

我不太了解您是否需要测试私有方法。根本问题是您的公共方法的返回类型为void,因此您无法测试您的公共方法。因此,您不得不测试您的私有方法。我的猜测正确吗?

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

  1. 模拟您的私有方法,但仍然不会“实际”测试您的方法。

  2. 验证方法中使用的对象的状态。大多数方法要么对输入值进行一些处理并返回输出,要么更改对象的状态。也可以采用测试对象的期望状态。

    public class A{
    
    SomeClass classObj = null;
    
    public void publicMethod(){
       privateMethod();
    }
    
    private void privateMethod(){
         classObj = new SomeClass();
    }
    
    }

    [在这里,您可以通过检查classObj从null到not null的状态变化来测试私有方法。

  3. 稍微重构代码(希望这不是遗留代码)。我写方法的基本原理是,应该总是返回一些内容(一个int /布尔值)。返回值可以或不可以被实现使用,但一定会被测试使用

    码。

    public class A
    { 
        public int method(boolean b)
        {
              int nReturn = 0;
              if (b == true)
                   nReturn = method1();
              else
                   nReturn = method2();
        }
    
        private int method1() {}
    
        private int method2() {}
    
    }

3

实际上,有一种方法可以通过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(); 会将您可以监视的内容存根给私人成员。


2

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


6
唯一合理的理由是测试遗留系统的一部分。如果您开始测试private / package-private方法,则将公开对象内部。这样做通常会导致不良的可重构代码。PRefer组成,因此您可以利用面向对象系统的所有优点来实现可测试性。
布莱斯2012年

1
同意-那将是首选方式。但是,如果您真的想使用Mockito测试私有方法,则这是唯一的(类型安全)选项。我的回答有点草率,我应该指出风险,就像您和其他人所做的那样。
罗兰·施耐德

这是我的首选方式。在包专用级别公开内部对象没有任何问题。单元测试是白盒测试,您需要了解内部测试。
Andrew Feng

0

如果private方法不是void并且返回值用作外部依赖项的方法的参数,则可以模拟该依赖项并使用an ArgumentCaptor来捕获返回值。例如:

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