;
Stub:For replacing a method with code that returns a specified result
Mock:A stub with an expectations that the method gets called
通过设置预期明确 Mock 对象执行时会发生什么,比如返回特定的值、抛出一个异常、触发一个事件等,又或者调用一定的次数。
验证预期的结果
设置预期和验证预期是同时进行的。设置预期在调用测试类的函数之前完成,验证预期则在它之后。所以,首先你设定好预期结果,然后去验证你的预期结果是否正确。
Mock的好处是什么
提前创建测试; TDD(测试驱动开发)
这是个最大的好处吧。如果你创建了一个Mock,那么你就可以在service接口创建之前写Service Tests了,这样你就能在开发过程中把测试添加到你的自动化测试环境中了。换句话说,模拟使你能够使用测试驱动开发。
团队可以并行工作
这类似于上面的那点;为不存在的代码创建测试。但前面讲的是开发人员编写测试程序,这里说的是测试团队来创建。当还没有任何东西要测的时候,测试团队如何来创建测试呢?模拟并针对模拟测试!这意味着当service接口需要测试时,实际上QA团队已经有了一套完整的测试组件;没有出现一个团队等待另一个团队完成的情况。这使得模拟的效益型尤为突出了。
你可以创建一个验证或者演示程序
由于Mocks非常高效,Mocks可以用来创建一个概念证明,作为一个示意图,或者作为一个你正考虑构建项目的演示程序。这为你决定项目接下来是否要进行提供了有力的基础,但最重要的还是提供了实际的设计决策。
为无法访问的资源编写测试
这个好处不属于实际效益的一种,而是作为一个必要时的“救生圈”。有没有遇到这样的情况?当你想要测试一个service接口,但service需要经过防火墙访问,防火墙不能为你打开或者你需要认证才能访问。遇到这样情况时,你可以在你能访问的地方使用MockService替代,这就是一个“救生圈”功能。
Mock可以交给用户
在有些情况下,某种原因你需要允许一些外部来源访问你的测试系统,像合作伙伴或者客户。这些原因导致别人也可以访问你的敏感信息,而你或许只是想允许访问部分测试环境。在这种情况下,如何向合作伙伴或者客户提供一个测试系统来开发或者做测试呢?最简单的就是提供一个mock,无论是来自于你的网络或者客户的网络。soapUI mock非常容易配置,他可以运行在soapUI或者作为一个war包发布到你的java服务器里面。
有时,你希望在没有系统其他部分的影响下测试系统单独的一部分。由于其他系统部分会给测试数据造成干扰,影响根据数据收集得到的测试结论。使用mock,你可以移除掉除了需要测试部分的系统依赖的模拟。当隔离这些mocks后,mocks就变得非常简单可靠,快速可预见。这为你提供了一个移除了随机行为,有重复模式并且可以监控特殊系统的测试环境。
Mockito
Mockito是一个简单的流行的Mock框架。它能够帮我们创建Mock对象,保持单元测试的独立性。
使用它只需要在 Maven 中添加依赖即可:
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>2.0.2-beta</version>
</dependency>
创建 Mock 对象
通过方法创建:
class CreateMock {
@Before
public void setup() {
mockUserDao = mock(UserDao.class);
userService = new UserServiceImpl();
userService.setUserDao(mockUserDao);
通过注解创建:
class CreateMock {
@Mock
UserDao mockUserDao;
@InjectMocks
private UserServiceImpl userService;
@Before
public void setUp() {
//初始化对象的注解
MockitoAnnotations.initMocks(this);
一个简单的例子:
package Mockito;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import static org.mockito.Mockito.*;
public class MyTest {
@Test
public void myTest() {
/* 创建 Mock 对象 */
List list = mock(List.class);
/* 设置预期,当调用get(0)方法时返回 "111" */
when(list.get(0)).thenReturn("111");
Assert.assertEquals("asd", 1, 1);
/* 设置后返回期望的结果 */
System.out.println(list.get(0));
/* 没有设置则返回 null */
System.out.println(list.get(1));
/* 对Mock对象设置无效 */
list.add("12");
list.add("123");
/* 返回之前设置的结果 */
System.out.println(list.get(0));
/* 返回 null */
System.out.println(list.get(1));
/* size大小为 0 */
System.out.println(list.size());
/* 验证操作,验证get(0)调用了 2 次 */
verify(list, times(2)).get(0);
/* 验证返回结果 */
String ret = (String)list.get(0);
Assert.assertEquals(ret, "111");
从上面代码能看出一般使用的步骤:
设置方法预期返回
通过 when(list.get(0)).thenReturn(“111”); 来设置当调用 list.get(0) 时返回 “111”,该方法就是Stub,替换我们实际的操作。
验证方法调用
该方法验证 get(0) 方法调用的次数 verify(list, times(2)).get(0); ,还可以设置是否调用过,调用时间等等。
验证返回值
Assert.assertEquals(ret, “111”); 方法验证Mock对象方法调用后的返回值是否达到预期。
Mockito使用
设置Mock对象期望和返回值
/* 表示第一次调用 someMethod() 返回 value1 第二次调用返回 value2 */
when(mock.someMethod()).thenReturn(value1).thenReturn(value2);
when(mock.someMethod()).thenReturn(value1, value2);
/* 也可以设置两次 */
when(mock.someMethod()).thenReturn(value1);
when(mock.someMethod()).thenReturn(value2);
另外一种写法 doReturn()
/* 表示第一次调用 someMethod() 返回 value1 第二次调用返回 value2 */
doReturn(value1).doReturn(value2).when(mock).someMethod();
/* 若返回 void,则设置为 doNothing() */
doNothing().when(mock).someMethod();
对方法设定返回异常
/* 当调用 someMethod() 方法时会抛出异常 */
when(mock.someMethod()).thenThrow(new RuntimeException());
/* 对 void 方法设定 */
doThrow(new RuntimeException()).when(mock).someMethod();
参数匹配器
我们不一定要固定Stub调用时的参数,如 get(0) 。可以通过参数匹配器来调用。
when(list.get(anyInt())).thenReturn("hello");
Mock 对象的行为验证
package Mockito;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.testng.annotations.Test;
import java.util.List;
import static org.mockito.Mockito.*;
* Created by andy.wwh on 2016/7/18.
public class Behavior {
@Test
public void behaviorCheck() {
List mock1 = mock(List.class);
List mock2 = mock(List.class);
/* 设置预期 */
when(mock1.get(0)).thenReturn("hello world");
when(mock1.get(1)).thenReturn("hello world");
when(mock2.get(0)).thenReturn("hello world");
mock1.get(0);
/* 验证方法调用一次 */
verify(mock1).get(0);
mock1.get(0);
/* 验证方法调用两次 */
verify(mock1, times(2)).get(0);
/* 验证方法从未被调用过 */
verify(mock2, never()).get(0);
/* 验证方法 100 毫秒内调用两次 */
verify(mock1, timeout(100).times(2)).get(anyInt());
/* 设置方法调用顺序 */
InOrder inOrder = inOrder(mock1, mock2);
inOrder.verify(mock1, times(2)).get(0);
inOrder.verify(mock2, never()).get(1);
/* 查询是否存在被调用,但未被 verify 验证的方法 */
verifyNoMoreInteractions(mock1, mock2);
/* 验证 Mock 对象是否没有交发生 */
verifyZeroInteractions(mock1, mock2);
/* 参数捕获器 */
ArgumentCaptor<Integer> argumentCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mock1, times(2)).get(argumentCaptor.capture());
System.out.println("argument:" + argumentCaptor.getValue());
验证调用次数
verify(mock1, timeout(100).times(2)).get(anyInt());
除了代码中的方法,Mockito 还提供了
- never() 没有被调用,相当于times(0)
- atLeast(N) 至少被调用N次
- atLeastOnce() 相当于atLeast(1)
- atMost(N) 最多被调用N次
通过 timeout 我们可以进行验证程序执行时间是否符合规则。
方法调用顺序
Inorder 可以验证方法调用的顺序
verifyNoMoreInteractions 和 verifyZeroInteractions
verifyNoMoreInteractions:查询是否存在被调用,但未被 verify 验证的方法
verifyZeroInteractions:verifyZeroInteractions
ArgumentCaptor 参数捕获器
可在验证时对方法的参数进行捕获,最后验证捕获的参数值。如果方法有多个参数都要捕获验证,那就需要创建多个ArgumentCaptor对象处理。
Spy 对象验证
Mock操作的全是虚拟对象。即使我们设置了 when(list.get(0)).thenReturn(1) ,我们调用如 size() 方法返回的还是0。Mockito还给我们提供了一种对真实对象操作的方法——Spy
做一个简单的比较:
package Mockito;
import org.testng.annotations.Test;
import java.util.List;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
* Created by andy.wwh on 2016/7/18.
public class MockObject {
@Test
public void MockTest() {
List list = mock(List.class);
when(list.get(0)).thenReturn("hello world");
System.out.println(list.get(0));
System.out.println(list.size());
package Mockito;
import org.testng.annotations.Test;
import java.util.LinkedList;
import java.util.List;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
* Created by andy.wwh on 2016/7/18.
public class MockObject {
@Test
public void MockTest() {
/* 创建真实对象 */
List list = new LinkedList();
List spy = spy(list);
spy.add("hello");
when(spy.get(0)).thenReturn("hello world");
System.out.println(spy.get(0));
看个官网的例子:
@Test
public void spyTest() {
List list = new LinkedList();
List spy = spy(list);
// optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);
// using the spy calls real methods
spy.add("one");
spy.add("two");
// prints "one" - the first element of a list
System.out.println(spy.get(0));
// size() method was stubbed - 100 is printed
System.out.println(spy.size());
// optionally, you can verify
verify(spy).add("one");
verify(spy).add("two");
Mockito 初探
Mockito:一个强大的用于 Java 开发的模拟测试框架
Min是清明的茗