Mock Server利器 - Moco&Mockito
Mockito简介
什么是mock?
在软件开发的世界之外, "mock"一词是指模仿或者效仿。 因此可以将“mock”理解为一个替身,替代者. 在软件开发中提及"mock",通常理解为模拟对象或者Fake。
为什么需要Mock?
Mock是为了解决units之间由于耦合而难于被测试的问题。所以mock object是unit test的一部分。
Mock的好处是什么?
提前创建测试,TDD(测试驱动开发)
这是个最大的好处吧。如果你创建了一个Mock那么你就可以在service接口创建之前写Service Tests了,这样你就能在开发过程中把测试添加到你的自动化测试环境中了。换句话说,模拟使你能够使用测试驱动开发。
团队可以并行工作
这类似于上面的那点;为不存在的代码创建测试。但前面讲的是开发人员编写测试程序,这里说的是测试团队来创建。当还没有任何东西要测的时候测试团队如何来创建测试呢?模拟并针对模拟测试!这意味着当service借口需要测试时,实际上QA团队已经有了一套完整的测试组件;没有出现一个团队等待另一个团队完成的情况。这使得模拟的效益型尤为突出了。
你可以创建一个验证或者演示程序。
由于Mocks非常高效,Mocks可以用来创建一个概念证明,作为一个示意图,或者作为一个你正考虑构建项目的演示程序。这为你决定项目接下来是否要进行提供了有力的基础,但最重要的还是提供了实际的设计决策。
为无法访问的资源编写测试
这个好处不属于实际效益的一种,而是作为一个必要时的“救生圈”。有没有遇到这样的情况?当你想要测试一个service接口,但service需要经过防火墙访问,防火墙不能为你打开或者你需要认证才能访问。遇到这样情况时,你可以在你能访问的地方使用MockService替代,这就是一个“救生圈”功能。
Mock 可以交给用户
在有些情况下,某种原因你需要允许一些外部来源访问你的测试系统,像合作伙伴或者客户。这些原因导致别人也可以访问你的敏感信息,而你或许只是想允许访问部分测试环境。在这种情况下,如何向合作伙伴或者客户提供一个测试系统来开发或者做测试呢?最简单的就是提供一个mock,无论是来自于你的网络或者客户的网络。soapUI mock非常容易配置,他可以运行在soapUI或者作为一个war包发布到你的java服务器里面。
隔离系统
有时,你希望在没有系统其他部分的影响下测试系统单独的一部分。由于其他系统部分会给测试数据造成干扰,影响根据数据收集得到的测试结论。使用mock你可以移除掉除了需要测试部分的系统依赖的模拟。当隔离这些mocks后,mocks就变得非常简单可靠,快速可预见。这为你提供了一个移除了随机行为,有重复模式并且可以监控特殊系统的测试环境。
Mockito使用示例
模拟对象
// 模拟LinkedList 的一个对象
LinkedList mockedList = mock(LinkedList.class);
// 此时调用get方法,会返回null,因为还没有对方法调用的返回值做模拟
System.out.println(mockedList.get(0));
模拟方法调用的返回值
// 模拟获取第一个元素时,返回字符串first。 给特定的方法调用返回固定值在官方说法中称为stub。
when(mockedList.get(0)).thenReturn("first");
// 此时打印输出first
System.out.println(mockedList.get(0));
模拟方法调用抛出异常
// 模拟获取第二个元素时,抛出RuntimeException
when(mockedList.get(1)).thenThrow(new RuntimeException());
// 此时将会抛出RuntimeException
System.out.println(mockedList.get(1));
如果一个函数没有返回值类型,那么可以使用此方法模拟异常抛出
doThrow(new RuntimeException("clear exception")).when(mockedList).clear();
mockedList.clear();
模拟调用方法时的参数匹配
// anyInt()匹配任何int参数,这意味着参数为任意值,其返回值均是element
when(mockedList.get(anyInt())).thenReturn("element");
// 此时打印是element
System.out.println(mockedList.get(999));
模拟方法调用次数
// 调用add一次
mockedList.add("once");
// 下面两个写法验证效果一样,均验证add方法是否被调用了一次
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
校验行为
// mock creation
List mockedList = mock(List.class);
// using mock object
mockedList.add("one");
mockedList.clear();
//verification
verify(mockedList).add("one");
verify(mockedList).clear();
模拟方法调用(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));
verify(mockedList).get(0);
参数匹配
//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());
//argument matchers can also be written as Java 8 Lambdas
verify(mockedList).add(someString -> someString.length() > 5);
校验方法调用次数
//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");
模拟无返回方法抛出异常
doThrow(new RuntimeException()).when(mockedList).clear();
//following throws RuntimeException:
mockedList.clear();
校验方法调用顺序
// 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
校验方法是否从未调用
//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);
快速创建Mock对象
public class ArticleManagerTest {
@Mock private ArticleCalculator calculator;
@Mock private ArticleDatabase database;
@Mock private UserProvider userProvider;
@Before
public void before(){
MockitoAnnotations.initMocks(this);
}
}
自定义返回不同结果
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")); // 第n次(n> 2),依旧以最后返回最后一个配置
对返回结果进行拦截
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"));
Mock函数操作
可以通过doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod() 来自定义函数操作。
暗中调用真实对象
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");
改变默认返回值
Foo mock = mock(Foo.class, Mockito.RETURNS_SMART_NULLS);
Foo mockTwo = mock(Foo.class, new YourOwnAnswer());
捕获函数的参数值
ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
verify(mock).doSomething(argument.capture());
assertEquals("John", argument.getValue().getName());
部分Mock
//you can create partial mock with spy() method:
List list = spy(new LinkedList());
//you can enable partial mock capabilities selectively on mocks:
Foo mock = mock(Foo.class);
//Be sure the real implementation is 'safe'.
//If real implementation throws exceptions or depends on specific state of the object then you're in trouble.
when(mock.someMethod()).thenCallRealMethod();
重置Mock
List mock = mock(List.class);
when(mock.size()).thenReturn(10);
mock.add(1);
reset(mock);
//at this point the mock forgot any interactions & stubbing
序列化
List<Object> list = new ArrayList<Object>();
List<Object> spy = mock(ArrayList.class, withSettings()
.spiedInstance(list)
.defaultAnswer(CALLS_REAL_METHODS)
.serializable());
检查超时
//passes when someMethod() is called within given time span
verify(mock, timeout(100)).someMethod();
//above is an alias to:
verify(mock, timeout(100).times(1)).someMethod();
//passes when som`eMethod() is called exactly 2 times within given time span
verify(mock, timeout(100).times(2)).someMethod();
//passes when someMethod() is called at least 2 times within given time span
verify(mock, timeout(100).atLeast(2)).someMethod();
//verifies someMethod() within given time span using given verification mode
//useful only if you have your own custom verification modes.
verify(mock, new Timeout(100, yourOwnVerificationMode)).someMethod();
Mock详情
Mockito.mockingDetails(someObject).isMock();
Mockito.mockingDetails(someObject).isSpy();
###############################################################
转载请标明出处:http://blog.csdn.net/shensky711/article/details/52770686
本文出自: 【HansChen的博客】
Moco介绍
在开发过程中,经常会使用到一些http网络接口,而这部分功能通常是由第三方开发团队或者是后端同事进行开发的,在我们开发时不能给我们提供服务,更有甚者,要集成的服务在开发时还不存在。这为我们的联调和测试造成了麻烦,常见的解决方案是搭建一个web server。
为什么要开发Moco这个框架?
具体到模拟服务上,处理的手法也是各种各样,因为服务以HTTP集成居多,无论是Web Service,还是REST,所以,一种典型的做法是,开发一个模拟服务,打成WAR包,部署到一个应用服务器上。而我们知道,一旦牵扯到应用服务器部署,就是非常耗时的,部署的时间量级通常是分钟级的。而且,模拟服务器通常不是一次性的工作,我们需要在开发过程中,反复调整,这就进一步增加了维护一个模拟服务器的成本。有的应用服务器是非常消耗资源的,要用专门的机器来部署它。更进一步,如果机器资源有限,团队就只能共享一台机器,这样,即便我为测试自己的部分做一个小的改动,很有可能因为得不到机器的使用权,而要等上几天时间
Moco就是针对这样一个特定的场景而生的。Moco是一个简单搭建模拟服务器的程序库/工具,这个基于 Java 开发的开源项目已经在 Github 上获得了不少的关注。该项目的简介是这样描述自己的:Moco 是一个简单搭建 stub 的框架,主要用于测试和集成。
开发团队只要根据自己的需要进行相应的配置,就会很方便得到一个模拟服务器。而且,由于 Moco 本身的灵活性,其用途已经不再局限于最初的集成测试,比如,Moco 可以用于移动开发,模拟尚未开发的服务;Moco 还可以用于前端开发,模拟一个完整的 Web 服务器等等。
Moco本身支持API和独立运行两种方式。通过使用API,开发人员可以在JUnit、JBehave等测试测试框架里使用Moco,极大程度地降低了集成点测试的复杂度
Moco可以提供以下服务:
- HTTP APIs
- Socket APIs
- REST API
Moco原理简介:Moco会根据一些配置,启动一个真正的HTTP服务(会监听本地的某个端口)。当发起请求满足一个条件时,它就给回复一个应答。Moco的底层没有依赖于像Servlet这样的重型框架,而是基于一个叫Netty网络应用框架直接编写的,这样一来,绕过了复杂的应用服务器,所以,它的速度是极快的
Moco已经在github上开源,可点击连接:https://github.com/dreamhead/moco
Moco独立运行所需环境
Moco独立运行时所需准备的有:
- Java运行环境
- moco-runner-0.11.0-standalone.jar
如何运行Moco
启动http服务
Moco的运行非常简单,只需要一行命令即可
如在命令行中运行:java -jar <path-to-moco-runner> http -p <monitor-port> -c < configuration -file>
-
<path-to-moco-runner>
:moco-runner-0.11.0-standalone.jar包的路径 -
<monitor-port>
:http服务监听的端口 -
<configuration -file>
:配置文件路径
这就在本地启动了一个http server,其中监听端口是12345,配置文件是MocoApi.json。只要在本机发起一个请求,如:http://localhost:12345,该请求就会被这个web server handle
如果别的机子想访问这个服务,只要把localhost替换成本机IP即可
启动https服务
启动https服务,需要先生成证书,并用如下命令启动服务:地方多发呆发地方的地方的地方的发呆发:java -jar <path-to-moco-runner> https -p <monitor-port> -c < configuration -file> --https <path-to-cert.jks > --cert mocohttps --keystore mocohttps
-
<path-to-moco-runner>
:moco-runner-0.11.0-standalone.jar包的路径 -
<monitor-port>
:http服务监听的端口 -
<configuration -file>
:配置文件路径 -
<path-to-cert.jks>
:证书路径
这就在本地启动了一个http server,其中监听端口是12346,配置文件是MocoApi.json,证书文件是test.cer
Moco HTTP(s) API配置
启动服务之后,必然会根据需求stub出各种各样接口反馈,我们会把这个配置放在一个json文件中,启动Moco的时候,需要指定使用的配置文件路径,这样配置就可以生效了。Moco服务可以检测到配置文件的变更,假如你修改了配置文件,不需要重新启动Moco,服务照样可以生效。更详细的配置介绍请查看:https://github.com/dreamhead/moco/blob/master/moco-doc/apis.md
配置文件的工作原理大致如下:
image_1aukcdkko1htkpba1hjt14srit213.png-53.4kB
如何在配置文件添加注释
json不支持注释,想要添加注释的话,可以在description字段中加入描述
约定请求Body
image_1aukcg2219uc4a89v2eia169i1t.png-63.5kB约定接口的uri
image_1aukcgr1h1pvg1iqede15mtjtv2a.png-35.2kB约定请求参数
image_1aukcheemo95pmp1q351n0vjq12n.png-40.6kB约定请求方法
image_1aukci164cq717av19nc1cp0b0q34.png-32.7kB约定HTTP版本
image_1aukcin2p1cnoenf1nfe1hn895u3h.png-38.2kB约定请求头部
image_1aukp23qh1io01g611k67l49ie9.png-47.6kB约定cookie
image_1aukp2ppph2215lv4mp165n1begm.png-45.5kB约定请求form
image_1aukp3eip1unv1s971l1s8ke1pr713.png-47.1kB表单可以添加多项,多项的时候,必须全部匹配,接口才算匹配成功
约定以指定xml作为请求body
image_1aukp4l8rblj9p712cq13au1nlc1g.png-69.9kB用xpath对请求进行匹配
image_1aukp54ud10dcbhi1a981sii1j1t.png-47.8kB约定以指定json作为请求body
image_1aukp62mg1c1p1ilpa371fg71okd2a.png-118.5kB用正则表达式对请求进行匹配
image_1aukp6kjk1cgktok1ksl3pl11ra2n.png-50.1kB匹配操作
image_1aukp79rk6stah19u7138o10df34.png-132kB设置Response content
image_1aukpa21f1nt21hrq15iv5111lcc3h.png-71kB设置Response 状态码
image_1aukpam111jae1kj31trj1vqau2d3u.png-50.7kB设置Response HTTP版本
image_1aukpb88b17f119i1oatsdi1pti4b.png-58.3kB设置Response 头部
image_1aukpboqh15l6ib31gvrcjo1dbq4o.png-45.9kB设置重定向
image_1aukpca591qrht381uv31cvt43g55.png-39.9kB设置cookie
image_1aukpcqun1tq911n314fv1o8r12hi5i.png-42.1kB挂载文件
image_1aukpdacf1sq3p241s14qbqdnb5v.png-34.4kBtemplate的用法
Moco内置了一些变量,在response中可以使用这些变量,让反馈更加智能,以下列举了常用的变量
- req.version
- req.version
- req.method
- req.content
- req.headers
- req.queries
- req.forms
- req.cookies
使用举例如下:
image_1aukpfh4037cdgjsdoevg10396c.png-114.1kB
Moco在单元测试中使用
Moco除了可以单独运行外,还可以在单元测试中运行,测试过程中,Moco会启动一个web server来处理我们的请求
image_1aukpgl0jlqs7fn10ha1e5n1kmu6p.png-156.2kB运行在单元测试中的moco server也可以选择加载json配置文件
image_1aukph7s4vpdjegi1fkp1jfn76.png-169.7kB
通过stub后台,便可对http请求进行测试了
Moco的不足
Moco的使用很简单,配置也很方便,目前更是提供了http、rest、socket服务。但是也仅仅是能stub出接口,模拟出简单的场景。如果接收到请求后需要做一些处理,如需查询数据库、进行运算、或者一些复杂的操作,就无能为力了。所以是否选用Moco,就取决于开发者是否只是需要一个简单的模拟服务器。