@RunWith(PowerMockRunner.class) 与 @RunWith(MockitoJUnitRunner.class) 的区别

22

在通常的mock测试中,使用@Mock@InjectMocks注释时,应该以@RunWith(MockitoJUnitRunner.class)运行正在进行测试的类。

@RunWith(MockitoJUnitRunner.class)
public class ReportServiceImplTestMockito {

     @Mock 
     private TaskService      mockTaskService;

     @InjectMocks 
     private ReportServiceImpl service;

         // Some tests
}

但在一些示例中,我看到使用@RunWith(PowerMockRunner.class)

@RunWith(PowerMockRunner.class)
public class Tests {
  @Mock
  private ISomething mockedSomething;

  @Test
  public void test1() {
    // Is the value of mockedSomething here
  }

  @Test
  public void test2() {
    // Is a new value of mockedSomething here
  }
}

有人能指出这两者的区别以及我应该在什么情况下使用其中之一吗?

3个回答

37
乍一看,答案很简单:有几个模拟框架可用,而使用它们的方式也不同。
第一个示例告诉JUnit使用Mockito模拟框架提供的“单元测试运行器”。第二个示例使用PowerMock框架的单元测试运行器。
为了使事情有意义,您还需要不同的导入语句,因为两个框架都对@Mock注释进行了不同的实现。
(使用这些特定于框架的测试运行器的主要目的是,它们可以使用特殊的框架特定注释初始化所有字段。)
所以:这里的区别仅仅是:第一个示例是使用Mockito框架编写的,第二个示例使用PowerMock。
那么,应该使用哪个?
答案:Mockito。
为什么?某种丑陋的真相是:PowerMock基本上是一声求救。它说:“被测试的类设计得很糟糕,请修复它。”也就是说:作为开发人员,您可以编写易于测试的代码或难以测试的代码。许多人采用后者:他们编写难以测试的代码。然后,PowerMock(ito)提供手段来仍然测试该代码。
PowerMock(ito)使您能够模拟(从而控制)对静态方法和new()的调用。为了实现这一点,PowerMock(ito)操纵您的被测试代码的字节码。对于小型代码库来说,这完全没问题,但当您面对数百万行生产代码和成千上万个单元测试时,情况就完全不同了。
我已经看到许多PowerMock测试失败,没有明显的原因,几个小时后才发现...某个其他地方的“静态”东西被更改了,而这种更改影响了一个不同的PowerMock静态/新驱动的测试案例。

我们的团队曾做出一个明智的决定:当你编写新代码,只能使用PowerMock进行测试是不可接受的。自那时以来,我们只创建了Mockito测试用例,并且从那以后再也没有遇到过类似的问题。

唯一可以使用PowerMock的情况是,当您想要测试现有(可能是第三方)代码,但又不想修改它时。但是,测试这样的代码有什么意义呢?当您无法修改该代码时,为什么测试会突然失败呢?


6
对于这场针对PowerMock的个人追求,到底怎么回事?我理解PowerMock确实是一个具有一定问题的模拟库,但它的创造者(顺便说一下,不是我,我创造了另一个库)旨在解决编写真实世界测试时最终出现的实际问题。只举一个非常合理的真实世界的情况来说明这样的工具很有用:模拟FacesContext.getCurrentInstance()静态方法,以便您可以验证对addMessage(...)方法的预期调用。此外,这里提出的其他声明要么事实上是错误的,要么存在高度争议。 - Rogério
我同意,开发人员不应该使用模拟库来避免解决可测试性问题。这与他们为了绕过模拟库的限制而避免良好的面向对象或API设计的情况一样真实。对我来说,这两种情况的最佳答案是完全避免模拟,并进行集成测试;这就是我自己的做法。 - Rogério
1
有合理的理由想要测试无法符合可测试设计的类,例如,如果您想要测试使用lwjgl的类,则必须创建一个仅调用lwjgl类静态方法的薄层,或者您可以为该类使用powermock。前者需要重新创建您正在使用的API并维护该层,更不用说在需要高性能的紧密循环中添加额外的调用了。通过具有自动化(并且经过测试)模拟框架的方式来测试该代码将远远优于根本不进行测试。 - Daniel Carlsson
1
@Eugene 实际上,你是对的。那是一个非常古老的答案,来自我每周必须修复此类PowerMock问题的时代。我重新制定了我的答案,我认为它A)现在回答了问题并且B)给出了更加平衡的观点。但是要明确一点:4年没有编写单个PowerMock测试...我百分之百地相信这有助于我们创建更好的代码。 - GhostCat
1
@GhostCat,非常感谢您的回答,因为它让我重新思考了我的测试和我试图测试的类。我重构了我正在测试的类,以消除一个一行代码的方法,然后仔细研究了代码后,我意识到我不需要使用PowerMock的whenNew。 - cptully
显示剩余4条评论

4

PowerMock不应该是你的首选。如果你只写了一个只能通过PowerMock测试的类,那么你做错了什么。一个类应该有依赖注入或带有依赖项的构造函数,这样可以方便地进行测试,当然:不要尝试使用静态方法,因为这些在常规框架(即mockito)中无法模拟。

另一方面:如果你有一个大型项目,并且想要添加单元测试,因为之前的开发人员没有做这样的事情,PowerMock可能是唯一的解决方案,而不是完全重构所有内容。从这个角度来看,我更喜欢PowerMock而不是完全没有测试

PowerMock很“脏”,因为它改变了字节码,JaCoCo(SonarQube覆盖率运行器)的代码覆盖率无法工作,但是IntelliJ的代码覆盖率运行器可以与PowerMock一起使用。

当一个类中有一个方法无法使用Mockito进行测试时,我会将测试拆分成两个测试类:一个使用Mockito,另一个使用PowerMock。这将使你的SonarQube代码覆盖率保持较好。

public class ClassToTest {

    public void testableMethod() {
        /* Do something */
    }

    public String methodWithStaticCall() {
        return MyTest.staticMethod();
    }
}

接下来我有一个类来测试第一个方法:

@RunWith(MockitoJUnitRunner.class)
public class testClassToTest() {
   private sut = new ClassToTest();

   @Test
   public testMethod() {
       sut.testableMethod();
   }
}

还有一个使用PowerMock的例子:

@RunWith(PowerMockJUnitRunner.class)
@PrepareForTest({MyTest.class, ClassToTest.class})
public class testClassToTestPM() {
   private sut = new ClassToTest();

   @Before
   public void before() {
       mockStatic(MyTest.class);
   }

   @Test
   public testMethod() {
       mockStatic(MyTest.class);
       when(MyTest.staticMethod()).thenReturn("test");
       assertEquals("test", sut.methodWithStaticCall());
   }
}

2

PowerMock 允许您模拟静态和私有方法,以及最终类等更多内容。

PowerMock 是一个框架,它扩展了其他模拟库(如 EasyMock)的功能。PowerMock 使用自定义类加载器和字节码操作来实现对静态方法、构造函数、最终类和方法、私有方法、移除静态初始化程序等的模拟。

如果您需要模拟这些类型的组件,则可能存在一些代码异味,但这可能是有用的。在某些情况下,您可能正在使用过时的项目,该项目创建了具有需要进行模拟的依赖关系的静态帮助器类。如果您有更改架构的能力,则修复您的设计!否则,请为您的测试选择正确的技术。

如果您不需要模拟静态或私有函数,则无需使用 PowerMock。PowerMock 是其他模拟框架的包装器。


1
可能确实存在某种代码异味,但在我们开始更改代码之前,我们可能需要一些测试来确保我们的更改不会破坏代码... ;) - Haakon Løtveit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接