概述

本文将开启一系列关于JMockit的教程。在这个系列的第一部分,我们将讨论什么是JMockit,它的特点,以及如何使用它创建和使用mock对象。

后续的文章会深入探讨它的功能。

2. JMockit

首先,让我们来了解一下JMockit:一个用于测试中模拟Java对象的框架(适用于JUnit和TestNG)。它利用Java的运行时字节码修改API,动态改变类的行为。它的亮点在于表达性以及对静态方法模拟的内置支持。

也许你对JMockit还不太熟悉,但这并不是因为它新。JMockit的开发始于2006年6月,首次稳定发布是在2012年12月,至今已有相当一段时间(本文写作时的最新版本是1.24)。

2.1. Maven依赖

在开始之前,我们需要在项目中添加jmockit依赖:

<dependency> 
    <groupId>org.jmockit</groupId> 
    <artifactId>jmockit</artifactId> 
    <version>1.49</version>
</dependency>

2.2. JMockit的表达性

正如前面所说,JMockit的强大之处之一在于其表达性。为了创建mock并定义其行为,我们不需要调用mocking API的方法,而是直接定义即可。

这意味着我们不会这样做:

API.expect(mockInstance.method()).andThenReturn(value).times(2);

相反,我们会这样做:

new Expectation() {
    mockInstance.method(); 
    result = value; 
    times = 2;
}

乍看之下可能代码更多,但实际上我们可以把三行合并成一行。真正重要的是,我们不会写出一大串链式调用。取而代之的是,我们明确地定义了当mock被调用时的行为。

考虑到result = value部分可以返回任何东西(固定值、动态生成的值、异常等),JMockit的表达性就更加明显了。

2.3. 录制-回放-验证模型

使用JMockit的测试分为三个阶段:录制、回放和验证。

  1. 录制阶段:在测试准备阶段和调用期望执行的方法之前,我们会为接下来的阶段定义所有测试的预期行为。
  2. 回放阶段:测试代码实际执行阶段,之前在录制阶段记录的mock方法/构造函数调用现在将被重放。
  3. 验证阶段:我们将断言测试结果是否符合预期(并且mock按照我们在录制阶段定义的行为工作并使用)。

下面是一个代码示例,测试的骨架看起来像这样:

@Test
public void testWireframe() {
   // preparation code not specific to JMockit, if any

   new Expectations() {{ 
       // define expected behaviour for mocks
   }};

   // execute code-under-test

   new Verifications() {{ 
       // verify mocks
   }};

   // assertions
}

3. 创建Mock

3.1. JMockit的注解

在JMockit中,使用mock的最简单方式是使用注解。有三种注解用于创建mock(@Mocked@Injectable@Capturing),以及一个用于指定测试类的注解(@Tested)。

在字段上使用@Mocked注解,它将为该特定类的新对象创建每个mock实例。

另一方面,@Injectable注解只会创建一个mock实例。

最后的注解@Capturing的行为类似于@Mocked,但会扩展到所有继承或实现注解字段类型的对象子类。

3.2. 将参数传递给测试

使用JMockit时,可以将mock作为测试参数传递。这对于仅在一个特定测试中需要特殊行为的复杂模型对象特别有用。例如:

public class TestPassingArguments {
   
   @Injectable
   private Foo mockForEveryTest;

   @Tested
   private Bar bar;

   @Test
   public void testExample(@Mocked Xyz mockForJustThisTest) {
       new Expectations() {{
           mockForEveryTest.someMethod("foo");
           mockForJustThisTest.someOtherMethod();
       }};

       bar.codeUnderTest();
   }
}

通过这种方式创建mock,而不是调用某个API方法,再次体现了我们一直在讨论的表达性。

3.3. 完整示例

为了结束这篇文章,我们将提供一个使用JMockit的完整测试示例。

在这个例子中,我们将测试一个Performer类,它在其perform()方法中使用Collaboratorperform()方法接收一个Model对象作为参数,从中使用getInfo()方法获取一个字符串,这个字符串会被传递给Collaboratorcollaborate()方法,该方法在这个特定测试中返回true,然后这个值会被传递给Collaboratorreceive()方法。

所以,测试的类看起来如下:

public class Model {
    public String getInfo(){
        return "info";
    }
}

public class Collaborator {
    public boolean collaborate(String string){
        return false;
    }
    public void receive(boolean bool){
        // NOOP
    }
}

public class Performer {
    private Collaborator collaborator;
    
    public void perform(Model model) {
        boolean value = collaborator.collaborate(model.getInfo());
        collaborator.receive(value);
    }
}

而测试代码如下:

public class PerformerTest {

    @Injectable
    private Collaborator collaborator;

    @Tested
    private Performer performer;

    @Test
    public void testThePerformMethod(@Mocked Model model) {
        new Expectations() {{
            model.getInfo();result = "bar";
            collaborator.collaborate("bar"); result = true;
        }};
        
        performer.perform(model);
        
        new Verifications() {{
            collaborator.receive(true);
        }};
    }
}

4. 结论

至此,我们结束了对JMockit的基础介绍。如果你想了解更多关于JMockit的内容,请期待后续文章。

本教程的完整实现可以在GitHub项目中找到。

4.1. 系列文章

整个系列的所有文章: