概述
本文将开启一系列关于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的测试分为三个阶段:录制、回放和验证。
- 录制阶段:在测试准备阶段和调用期望执行的方法之前,我们会为接下来的阶段定义所有测试的预期行为。
- 回放阶段:测试代码实际执行阶段,之前在录制阶段记录的mock方法/构造函数调用现在将被重放。
- 验证阶段:我们将断言测试结果是否符合预期(并且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()
方法中使用Collaborator
。perform()
方法接收一个Model
对象作为参数,从中使用getInfo()
方法获取一个字符串,这个字符串会被传递给Collaborator
的collaborate()
方法,该方法在这个特定测试中返回true
,然后这个值会被传递给Collaborator
的receive()
方法。
所以,测试的类看起来如下:
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. 系列文章
整个系列的所有文章: