美文网首页我爱编程
Mock Server利器 - Moco&Mockito

Mock Server利器 - Moco&Mockito

作者: 燕京博士 | 来源:发表于2018-05-28 12:15 被阅读342次

    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>:配置文件路径
    image_1aukc7jbn1rh51p2ma761dd5v4j9.png-52.4kB

    这就在本地启动了一个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>:证书路径
    image_1aukcamtmf2v14d14pi1r5ria6m.png-46.1kB

    这就在本地启动了一个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字段中加入描述

    image_1aukces4c1bld1ilv120vtc713rk1g.png-21.5kB

    约定请求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.4kB

    template的用法

    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,就取决于开发者是否只是需要一个简单的模拟服务器。

    相关文章

      网友评论

        本文标题:Mock Server利器 - Moco&Mockito

        本文链接:https://www.haomeiwen.com/subject/thzwjftx.html