当前位置: 移动技术网 > IT编程>开发语言>Java > Java测试框架Mockito的简明教程

Java测试框架Mockito的简明教程

2019年07月19日  | 移动技术网IT编程  | 我要评论

什么是 mock 测试

mock 测试就是在测试过程中,对于某些不容易构造(如 httpservletrequest 必须在servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 jdbc 中的resultset 对象),用一个虚拟的对象(mock 对象)来创建以便测试的测试方法。

mock 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。

比如一段代码有这样的依赖:

当我们需要测试a类的时候,如果没有 mock,则我们需要把整个依赖树都构建出来,而使用 mock 的话就可以将结构分解开,像下面这样:

mock 对象使用范畴

真实对象具有不可确定的行为,产生不可预测的效果(如:股票行情,天气预报) :

  • 真实对象很难被创建的
  • 真实对象的某些行为很难被触发
  • 真实对象实际上还不存在的(和其他开发小组或者和新的硬件打交道)等等

使用 mock 对象测试的关键步骤

  • 使用一个接口来描述这个对象
  • 在产品代码中实现这个接口
  • 在测试代码中实现这个接口
  • 在被测试代码中只是通过接口来引用对象,所以它不知道这个引用的对象是真实对象,还是 mock 对象。

mock 与stub 的区别

mock 不是 stub,两者是有区别的:

  • 前者被称为 mockist tdd,而后者一般称为 classic tdd ;
  • 前者是基于行为的验证(behavior verification),后者是基于状态的验证 (state verification);
  • 前者使用的是模拟的对象,而后者使用的是真实的对象。

java mock 测试

目前,在 java 阵营中主要的 mock 测试工具有 mockito,jmock,easymock 等。

关于这些框架的比较,不是本文的重点。本文着重介绍 mockito 的使用。

mockito 的特性

mockito 是美味的 java 单元测试 mock 框架,开源。

大多 java mock 库如 easymock 或 jmock 都是 expect-run-verify (期望-运行-验证)方式,而 mockito 则使用更简单,更直观的方法:在执行后的互动中提问。使用 mockito,你可以验证任何你想要的。而那些使用 expect-run-verify 方式的库,你常常被迫查看无关的交互。

非 expect-run-verify 方式 也意味着,mockito 无需准备昂贵的前期启动。他们的目标是透明的,让开发人员专注于测试选定的行为。

mockito 拥有的非常少的 api,所有开始使用 mockito,几乎没有时间成本。因为只有一种创造 mock 的方式。只要记住,在执行前 stub,而后在交互中验证。你很快就会发现这样 tdd java 代码是多么自然。

类似 easymock 的语法来的,所以你可以放心地重构。mockito 并不需要“expectation(期望)”的概念。只有 stub 和验证。

mockito 实现了 gerard meszaros 所谓的 test spy.

其他的一些特点:

  • 可以 mock 具体类而不单止是接口
  • 一点注解语法糖 - @mock
  • 干净的验证错误是 - 点击堆栈跟踪,看看在测试中的失败验证;点击异常的原因来导航到代码中的实际互动。堆栈跟踪总是干干净净。
  • 允许灵活有序的验证(例如:你任意有序 verify,而不是每一个单独的交互)
  • 支持“详细的用户号码的时间”以及“至少一​​次”验证
  • 灵活的验证或使用参数匹配器的 stub (anyobject(),anystring() 或 refeq() 用于基于反射的相等匹配)
  • 允许创建自定义的参数匹配器或者使用现有的 hamcrest 匹配器

mockito 入门

声明 mockito 依赖

gradle 用户可以使用:

repositories { jcenter() }
dependencies { testcompile "org.mockito:mockito-core:1.+" }

示例

1.验证行为

//let's import mockito statically so that the code looks clearer
import static org.mockito.mockito.*;
//mock creation
list mockedlist = mock(list.class);
//using mock object
mockedlist.add("one");
mockedlist.clear();
//verification
verify(mockedlist).add("one");
verify(mockedlist).clear();

一旦创建 mock 将会记得所有的交互。你可以选择验证你感兴趣的任何交互

2.stubbing

//you can mock concrete classes, not just interfaces
linkedlist mockedlist = mock(linkedlist.class);
//stubbing
when(mockedlist.get(0)).thenreturn("first");
when(mockedlist.get(1)).thenthrow(new runtimeexception());
//following prints "first"
system.out.println(mockedlist.get(0));
//following throws runtime exception
system.out.println(mockedlist.get(1));
//following prints "null" because get(999) was not stubbed
system.out.println(mockedlist.get(999));
//although it is possible to verify a stubbed invocation, usually it's just redundant
//if your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).
//if your code doesn't care what get(0) returns, then it should not be stubbed. not convinced? see here.
verify(mockedlist).get(0);

  • 默认情况下,所有方法都会返回值,一个 mock 将返回要么 null,一个原始/基本类型的包装值或适当的空集。例如,对于一个 int/integer 就是 0,而对于 boolean/boolean 就是 false。
  • stubbing 可以被覆盖。
  • 一旦 stub,该方法将始终返回一个 stub 的值,无论它有多少次被调用。
  • 最后的 stubbing 是很重要的 - 当你使用相同的参数 stub 多次同样的方法。换句话说:stubbing 的顺序是重要的,但它唯一有意义的却很少,例如当 stubbing 完全相同的方法调用,或者有时当参数匹配器的使用,等等。

3.参数匹配器

mockito 验证参数值使用 java 方式:通过使用 equals() 方法。有时,当需要额外的灵活性,可以使用参数匹配器:

//stubbing using built-in anyint() argument matcher
when(mockedlist.get(anyint())).thenreturn("element");
//stubbing using custom matcher (let's say isvalid() returns your own matcher implementation):
when(mockedlist.contains(argthat(isvalid()))).thenreturn("element");
//following prints "element"
system.out.println(mockedlist.get(999));
//you can also verify using an argument matcher
verify(mockedlist).get(anyint());

参数匹配器允许灵活的验证或 stubbing。点击这里查看更多内置的匹配器和自定义的参数匹配器/ hamcrest匹配器的例子。

自定义参数的匹配信息,请查看 javadoc 中 argumentmatcher 类。

如果你正在使用参数的匹配,所有的参数都由匹配器来提供。

下面的示例演示验证,但同样适用于 stubbing:

verify(mock).somemethod(anyint(), anystring(), eq("third argument"));
//above is correct - eq() is also an argument matcher
verify(mock).somemethod(anyint(), anystring(), "third argument");
//above is incorrect - exception will be thrown because third argument is given without an argument matcher.

4.调用额外的调用数字/at least x / never

//using mock
mockedlist.add("once");
mockedlist.add("twice");
mockedlist.add("twice");
mockedlist.add("three times");
mockedlist.add("three times");
mockedlist.add("three times");
//following two verifications work exactly the same - times(1) is used by default
verify(mockedlist).add("once");
verify(mockedlist, times(1)).add("once");
//exact number of invocations verification
verify(mockedlist, times(2)).add("twice");
verify(mockedlist, times(3)).add("three times");
//verification using never(). never() is an alias to times(0)
verify(mockedlist, never()).add("never happened");
//verification using atleast()/atmost()
verify(mockedlist, atleastonce()).add("three times");
verify(mockedlist, atleast(2)).add("five times");
verify(mockedlist, atmost(5)).add("three times");

times(1) 是默认的,因此,使用的 times(1) 可以显示的省略。

5.stubbing void 方法处理异常

dothrow(new runtimeexception()).when(mockedlist).clear();
//following throws runtimeexception:
mockedlist.clear();

6.有序的验证

// a. single mock whose methods must be invoked in a particular order
list singlemock = mock(list.class);
//using a single mock
singlemock.add("was added first");
singlemock.add("was added second");
//create an inorder verifier for a single mock
inorder inorder = inorder(singlemock);
//following will make sure that add is first called with "was added first, then with "was added second"
inorder.verify(singlemock).add("was added first");
inorder.verify(singlemock).add("was added second");
// b. multiple mocks that must be used in a particular order
list firstmock = mock(list.class);
list secondmock = mock(list.class);
//using mocks
firstmock.add("was called first");
secondmock.add("was called second");
//create inorder object passing any mocks that need to be verified in order
inorder inorder = inorder(firstmock, secondmock);
//following will make sure that firstmock was called before secondmock
inorder.verify(firstmock).add("was called first");
inorder.verify(secondmock).add("was called second");
// oh, and a + b can be mixed together at will

有序验证是为了灵活 - 你不必一个接一个验证所有的交互。

此外,您还可以通过创建 inorder 对象传递只与有序验证相关的 mock 。

7. 确保 mock 上不会发生交互

//using mocks - only mockone is interacted
mockone.add("one");
//ordinary verification
verify(mockone).add("one");
//verify that method was never called on a mock
verify(mockone, never()).add("two");
//verify that other mocks were not interacted
verifyzerointeractions(mocktwo, mockthree);

8.寻找多余的调用

//using mocks
mockedlist.add("one");
mockedlist.add("two");
verify(mockedlist).add("one");
//following verification will fail
verifynomoreinteractions(mockedlist);

注意:不建议 verifynomoreinteractions() 在每个测试方法中使用。 verifynomoreinteractions() 是从交互测试工具包一个方便的断言。只有与它的相关时才使用它。滥用它导致难以维护。

9. 标准创建 mock 方式 - 使用 @mock 注解

  • 最小化可重用 mock 创建代码
  • 使测试类更加可读性
  • 使验证错误更加易读,因为字段名称用于唯一识别 mock
public class articlemanagertest {
@mock private articlecalculator calculator;
@mock private articledatabase database;
@mock private userprovider userprovider;
private articlemanager manager;

在基础类或者测试 runner 里面,使用如下:

mockitoannotations.initmocks(testclass);

可以使用内建 runner: mockitojunitrunner 或者 rule: mockitorule

10. stubbing 连续调用(迭代器式的 stubbing)

when(mock.somemethod("some arg"))
.thenthrow(new runtimeexception())
.thenreturn("foo");
//first call: throws runtime exception:
mock.somemethod("some arg");
//second call: prints "foo"
system.out.println(mock.somemethod("some arg"));
//any consecutive call: prints "foo" as well (last stubbing wins).
system.out.println(mock.somemethod("some arg"));

下面是一个精简版本:

when(mock.somemethod("some arg"))
.thenreturn("one", "two", "three");

11. 回调 stubbing

允许使用泛型 answer 接口。

然而,这是不包括在最初的 mockito 另一个有争议的功能。我们建议您只需用thenreturn() 或 thenthrow() 来 stubbing ,这在测试/测试驱动中应用简洁与简单的代码足够了。但是,如果你有一个需要 stub 到泛型 answer 接口,这里是一个例子:

when(mock.somemethod(anystring())).thenanswer(new answer() {
object answer(invocationonmock invocation) {
object[] args = invocation.getarguments();
object mock = invocation.getmock();
return "called with arguments: " + args;
}
});
//the following prints "called with arguments: foo"
system.out.println(mock.somemethod("foo"));

12. doreturn()|dothrow()| doanswer()|donothing()|docallrealmethod() 家族方法

stubbing void 方法,需要不同的 when(object) ,因为编译器不喜欢括号内无效的方法…

在 用于 stubbing void 方法中,dothrow(throwable…) 取代 stubvoid(object)。主要原因是提高可读性和与 doanswer() 保持一致性。

当你想用 stub void 方法 使用 dothrow():

dothrow(new runtimeexception()).when(mockedlist).clear();
//following throws runtimeexception:
mockedlist.clear();

在调用 when() 的相应地方可以使用 othrow(), doanswer(), donothing(), doreturn() 和 docallrealmethod(),当:

  • stub void 方法
  • stub 方法在 spy 对象(见下面)
  • 可以不止一次的 stub 相同的方法,在测试的中期来改变 mock 的行为

但你更加倾向于使用这些方法来代替 when(),在所有的 stubbing 调用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网