转自:https://www.ibm.com/developerworks/cn/web/1405_liugang_mocowebtest/

Moco 框架以及其在 Web 集成测试的应用

我们往往将软件测试可以分为单元测试、集成测试、系统测试和验收测试。而集成测试界于单元测试和系统测试之间,起到"桥梁作用",一般由开发小组采用白盒加黑盒的方式来测试,既验证"设计",又验证"需求"。 主要用来测试模块与模块之间的接口,同时还要测试一些主要业务功能。集成测试(也叫组装测试,联合测试)是单元测试的逻辑扩展。它最简单的形式是:把两个已经测试过的单元组合成一个组件,测试它们之间的接口。从这一层意义上讲,组件是指多个单元的集成聚合。在现实方案中,许多单元组合成组件,而这些组件又聚合为程序的更大部分。方法是测试片段的组合,并最终扩展成进程,将模块与其他组的模块一起测试。最后,将构成进程的所有模块一起测试。此外,如果程序由多个进程组成,应该成对测试它们,而不是同时测试所有集成测试进程。

Web 集成测试

在很多时候,我们的 Web 应用程序往往会成为项目的瓶颈,以至于影响整个项目的按时发布。那么我们不禁要问,为什么会出现这样的情况?是什么在阻碍着项目的进度?在我们深入探讨这个问题之前,让我们先回忆一下一个典型的 Web 项目开发所经历的过程,从而寻找出问题的根源。

从开发的流程上看,它与其他的产品开发没有本质的区别,无外乎立项、审核、开发、集成、测试、维护这些具体的过程。每个步骤参与的人员和行为也都大同小异,然而您会发现往往很多的 Web 项目总是在集成测试环节出现这样或者那样的问题?(当然集成测试符合普氏原理,其他的产品开发也会遇到这样棘手的问题,只是 Web 显得更加的明显和突出罢了)其实这和具体的 Web 项目没有多大的关系,有点绕?哦,这句话的意思就是,我们需要将思维跳出具体的项目本身,从整个产品的角度来看待问题,这样就很容易找到痛点。那么让我们换个角度来看这个问题:

客户–〉Web/客户端–〉系统基础软件

从上面的关系中我们可以看到,作为"食物链"顶端的 Web 项目往往起着一个"中间人"的角色,在它之下是系统软件(如操作系统本身或者其上的中间件),而在它之上往往直接面对客户。在软件开发的经典教程里,总是将这种处于中间衔接层的软件设计称为--鲁棒性。也就是说它需要的是某种颠覆性的 OUTPUT,将具体的需求转化为实际的可以操作的架构设计,这样的一个"中间人"往往在一个产品的生命周期中扮演者举足轻重的角色。

Web 集成测试的痛点

而 Web 项目区别于其他项目的本质原因就在于,它直接面对需求的提出者--客户,而这部分的需求变动也是最为活跃的。也就是因为这样,往往很多时候我们对于 web 的开发要求的就是,快速!但这样的观点仅仅限于在 UI 或者 Web 编程方面,对于与底层系统的集成往往素手无策?因为底层系统一般来说是公司产品的核心内容,也遵循着典型的产品开发模式,在需求变更方面也显得很谨慎而小心。面对突入其来的的需求变动,底层系统往往很难跟的上节奏,而且很多时候 Web 的开发和底层系统的开发是分开的,或者分属于不同的开发部门,即使是一个方法级别的调用,就会浪费掉大家很多的沟通成本,接口的变更或者是
API 的向下兼容这些都是翻来覆去争论的话题。由于大家所处的开发领域不同,工作的方式,方法的不同,甚至是编程语言的差别,这些问题最终会暴露在集成测试这里,从而影响整体项目的开发进度。

这里就有一个真实的案例。在 Web 开发中,我们遇到了大数据显示的问题,分页显示无疑是最合适,也最很容易想到的解决方案。但由于其他的因素,底层系统无法在既定的项目期限内给予 API 方面的支持,在经过深入的沟通和相互理解之后发现,这样的功能在系统层面这个角度来说,属于优先级较低的功能。而且在他们开来,客户往往更习惯于使用命令行查看结果,并没有提出这样的需求,当然这样的争论是没有意义的,大家看待问题的角度不同,我们也不能一概而论,毕竟还是还有一些客户并不属于"专业级"用户,或者由于其他的原因的限制,他们不得不选择使用浏览器查看执行结果,面对这样的情况,摆在
Web 开发人员面前的问题就是如何在既定的情况下修改已有的结构,从而支持数据分页并且保证足够的可扩展性,以保证在未来底层系统可能支持的情况下,将代码修改的成本降低到最小?

解决思路和方案

问题已经暴露出来,让我们畅想一下期望的开发模式是什么,可能更容易让我们审核目前的问题,并找到最佳的解决方案。让我们畅想一下,如果我们在 Web 开发阶段没有这么多的顾虑而专注于功能本身,或者说我们总是假定底层系统足够的强大,并支持所有可能的的需求变更,是不是就没有这样的问题了?那么是否真的有这样的一个框架开始关注于这些问题,从而将 Web 开发人员解放出来,使其更加关注于自身领域的的需求呢?换句话说我们需要一个"模拟"的底层系统,它返回所有我们希望的结果,从而让我们将所有的注意力放在 Web 这个层面,对,这就是今天要介绍的
Moco!

Moco 是什么

作者是这样描述 Moco 的:

Moco是一个简单搭建模拟服务器的程序库/工具,这个基于 Java 开发的开源项目已经在 Github 上获得了不少的关注。该项目的简介是这样描述自己的:Moco
是一个简单搭建 stub 的框架,主要用于测试和集成。这个框架的开发灵感来自 Mock 框架,如 Mockito 和 Playframework

为什么要开发这个框架?

集成,尤其是基于 HTTP 协议的集成——web service、REST 等,在我们的项目开发中被广泛应用。以前,我们每次都要往 Jetty 或 Tomcat 等应用服务器上部署一个新的 WAR。大家都知道,开发部署一个 WAR 的过程是很枯燥的,即使在嵌入式服务器上也是如此。而且,每次我们做一点改动,整个 WAR 都要重新组装。

Moco 的出现,正是为了解决这些问题。开发团队只要根据自己的需要进行相应的配置,就会很方便得到一个模拟服务器。而且,由于 Moco 本身的灵活性,其用途已经不再局限于最初的集成测试,比如,Moco 可以用于移动开发,模拟尚未开发的服务;Moco 还可以用于前端开发,模拟一个完整的 Web 服务器,等等。

在 2013 年Duke's Choice Awards上,Moco 框架被提名为最具创新力的 Java 项目之一,在 Twitter 上得到了 Martin Fowler
的关注,详见《企业系统集成点测试策略》一文在 InfoQ 英文站

Moco 下的 Hello World

首先,您需要:

  1. 配置您的 Java 环境

    下载 Java 程序,并设置好系统环境变量 (PATH, JAVA_HOME)

  2. 安装并配置 Gradle

    具体可以参考http://www.gradle.org/

接下来,按照下面的步骤安装 Moco

  1. 获取 Moco 源文件

    • 使用 git 命令,获取最新的代码
    1
    git
    clone https://github.com/dreamhead/moco.git
    • 也可以直接下载编译好的 Jar 文件,目前是 0.9.1

    http://repo1.maven.org/maven2/com/github/dreamhead/moco-runner/0.9.1/moco-runner-0.9.1-standalone.jar

    1. 编译源代码,生成 Jar 文件 (用以编译好的 Jar 文件的,可以忽略这个步骤) 在命令行执行如下的命令
    1
    2
    cd
    <
    moco

    directory>
    ./gradlew
    build
  2. 编写配置文件,以简单的 Hello World 为例
    1
    2
    3
    4
    5
    6
    7
    8
    [
      {
        "response"
    :
          {
            "text"
    : "Hello, Moco"
          }
      }
    ]

    将文件以 json 的后缀存储,比如 foo.json

  3. 启动 Moco 服务

    在命令行输入

    1
    java
    -jar moco-runner-<
    version>-standalone.jar
    start -p 12306 -c foo.json

    注:-p 指定 Moco 服务端口 (目前仅指 Web 端口)

  4. 访问 Web 服务

    打开浏览器,访问 http://localhost:12306

    您应该可以立即看到久违了的"Hello World"

Moco 的复杂实例

实例一, 带参数的 HTTP 请求

有些时候我们希望能够在请求的同时,传递相应的参数。这时我们需要用到"queries"关键字

  • 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "request"
:
    {
      "uri"
: "/foo",
      "queries"
:
        {
          "param"
: "blah"
        }
    },
  "response"
:
    {
      "text"
: "bar"
    }
}
  • 启动浏览器,并访问
1
http://localhost:12306/foo?parm=blash

实例二,基于正则表达的 URL 匹配

在很多对 URL 请求的测试中,我们希望对于多个相似的 URL,都返回相同的结果

  • 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "request":
    {
      "uri":
        {
          "match":
"/\\w*/foo"
        }
    },
  "response":
    {
      "text":
"bar"
    }
}
  • 启动浏览器

在地址栏输入多个地址(/foo 前带有任意字符, 比如 http://localhost:12306/xxx/foo)

实例三:跳转

页面的自动跳转也是经常在 Web 开发中遇到的问题之一

  • 配置文件
1
2
3
4
5
6
7
{
  "request"
:
    {
      "uri"
: "/redirect"
    },
  "redirectTo"
: "http://www.github.com"
}
  • 启动浏览器访问 http://localhost:12306/redirect

页面将会被自动导向到 http://www.github.com

实例三: 返回 JSON 对象

在 Web 开发中,对于 JSON 的操作是最典型的应用

  • 配置文件
1
2
3
4
5
6
7
8
9
10
{
    "request":
{
        "uri":
"/json"
    },
    "response":
{
        "json":
{
            "foo"
: "bar"
        }
    }
}
  • 启动浏览器访问 http://localhost:12306/json

页面上会显示输出的 JSON 对象

Moco 高级用法

在 Moco 里您还可以发现一些好玩的,或者说高级用法,比如 Asynchronous、Template。具体用法还是参考 Moco 的文档,这里仅以 Asynchronous 为例。

  • 编写配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {
        "request":
    {
            "uri"
    : "/event"
        },
        "response":
    {
            "text":
    "event"
        },
        "on":
    {
            "complete":
    {
                "async"
    : "true",
                "post"
    : {
                    "url"
    : "http://another_site",
                    "content":
    "content"
                }
            }
        }
    }
  • 那么对于/event 的访问,将会是异步。

也就是说数据并不会立即返回,而是要等到对 http://another_siter 访问结束后,才会将结果放到 Response 里。

Moco 的 API 用法

前面的这些用法在 Moco 里被称为"Standalone", 它强调的是 Moco 的简单性和可配置性,而 Moco 的 API 是它的另一个特色,它更加关注如何在测试用例里如何使用 Moco。

我们先看一个基于 Moco 的典型测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import
org.junit.Test;
import
java.io.IOException;
import
com.github.dreamhead.moco.HttpServer;
import
org.apache.http.client.fluent.Content;
import
org.apache.http.client.fluent.Request;
import
com.github.dreamhead.moco.Runnable;
 
import
static com.github.dreamhead.moco.Moco.*;
import
static com.github.dreamhead.moco.Runner.*;
import
static org.hamcrest.core.Is.is;
import
static org.junit.Assert.assertThat;
 
@Test
public
void should_response_as_expected() throws Exception {
    HttpServer
server = httpserver(12306);
    server.response("foo");
 
    running(server,
new Runnable() {
        @Override
        public
void run() throws IOException {
            Content
content = Request.Get("http://localhost:12306").execute().returnContent();
            assertThat(content.asString(),
is("foo"));
        }
    });
}

上面的测试用例,描述了如何启动 Moco,以及调用相应的帮助方法来编写测试。

有时我们希望测试用例本身能够控制 Server 的启动和关闭,这里要用到@Before, @After 这些 Junit 里最常用的注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import
org.junit.After;
import
org.junit.Before;
import
org.junit.Test;
 
import
java.io.IOException;
 
import
static com.github.dreamhead.moco.Moco.httpserver;
import
static com.github.dreamhead.moco.Runner.runner;
import
static org.hamcrest.CoreMatchers.is;
import
static org.junit.Assert.assertThat;
 
public
class MocoRunnerTest {
    private
Runner runner;
 
    @Before
    public
void setup() {
        HttpServer
server = httpserver(12306);
        server.response("foo");
        runner
= runner(server);
        runner.start();
        helper
= new MocoTestHelper();
    }
 
    @After
    public
void tearDown() {
        runner.stop();
    }
 
    @Test
    public
void should_response_as_expected() throws IOException {
        Content
content = Request.Get("http://localhost:12306").execute().returnContent();
        assertThat(content.asString(),
is("foo"));
    }
}

Moco + Web 集成测试案例

了解了 Moco 的具体使用方法,我们来看一个真实的基于 Moco 的案例,从而理解为什么 Moco 对于 Web 开发人员来说是革命性的!

用例:

在 Web 上调用 Ajax 获取服务器端的版本(version)号,并根据返回的值显示不同的提示信息。

分析:

其实面对这样的需求,我们很容易看出这个用例本身,关注的是"显示不同的提示信息"这个功能,至于返回的 version 值,用户根本不必关心或者说这属于系统内部的逻辑。

我们先来看看以前的做法

  1. 安装并配置好 Web Server (Tomcat or Apache)
  2. 建立必要的 Web 工程文件,导入与项目相关的框架,比如 struts2
  3. 如果底层系统是 Java 本身,需要导入或者编译相关的 jar 文件,如果底层用其他的语言开发,就需要步骤 4
  4. 搭建底层开发环境或者在编译机器上编译 Lib 或者 so 文件,在确保能够运行的情况下(需要测试脚本)并导入到项目当中
  5. 发布 Web 应用并测试,如果底层 API 有变动或者方法调用错误,需要再次运行步骤 4。
  6. 编写并测试 Ajax 逻辑。

那么我们看看基于 Moco 的开发步骤会是什么样的:

  • 编写配置文件, 这里要用到刚才说的同步方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [
     {
     "env"
    : "remote",
     "include":
    "foo.json",
     },
     {
     "env"
    : "local",
     "include":
    "bar.json",
     }
    ]
  • 启动 Moco 服务
    1
    java
    -jar moco-runner-<
    version>-standalone.jar
    start -p 12306 -g env.json -e remote

    在很多时候,我们往往将时间浪费在步骤 3,4 中,即使已经有编译好的 Lib 或者 so,还是存在着测试的必要和风险。Web 的开发人员一方面要完成自己开发任务,另一方面还要扮演底层代码测试员角色。

  • 编写 Web 应用,调用 ajax 请求

    Moco 让开发人员更关注应用本身,而不必将时间和精力花费在 Lib 或者 so 的可用性上,而且没有复杂的 Web 容器配置,关注的就是切实的功能本身。

同时,它的 API 有利于 Web 开发者编写测试用例,将以前部门之间的无效沟通,建立在可度量的测试用例之上,从而提高了 Web 集成测试的可靠性,并有效地降低了集成测试的风险!

Moco 框架以及其在 Web 集成测试的应用的更多相关文章

  1. 郑晔谈 Moco 框架的开发:写一个好的内部 DSL ,写一个表达性好的程序

    作者:张龙 出处:http://www.infoq.com/cn/news/2013/07/zhengye-on-moco 郑晔谈Moco框架的开发:写一个好的内部DSL,写一个表达性好的程序 作者  ...

  2. 『居善地』接口测试 — 12、Moco框架介绍

    目录 1.Mock功能介绍 2.Moco框架介绍 3.Moco框架在接口测试中的作用 4.Moco框架的优点 5.Moco框架的下载与启动 (1)Moco框架的下载 (2)Moco框架的启动 1.Mo ...

  3. 十七、EnterpriseFrameWork框架核心类库之Web控制器

    回<[开源]EnterpriseFrameWork框架系列文章索引> EFW框架源代码下载:http://pan.baidu.com/s/1qWJjo3U EFW框架中的WebContro ...

  4. Mock1 moco框架的基本介绍

    前言: Mock就是模拟接口的,一般在开发人员还没有开发完接口,但是有接口文档,这个时候就可以执行接口测试,前端同学也可以用mock功能给自己使用. 功能:可以模拟http协议发送请求 下载链接:ht ...

  5. Mockserver之Moco框架搭建使用

    客户要求进行完整的产品展示,人员和时间都有限,来不及开发后端服务... 工期比较紧的项目,前端已开发完成,需要调试,后端接口还未开发完成... 公司某个项目依赖于第三方服务,但是第三方服务不方便频繁调 ...

  6. 『居善地』接口测试 — 13、Moco框架的使用

    目录 1.Moco框架第一个练习 2.Get方法的Mock实现 3.Post方法的Mock实现 4.请求中加入Cookies 5.请求中加入Header 6.Moco模拟重定向 7.综合练习 8.总结 ...

  7. 从零开始编写自己的C#框架(18)——Web层后端权限模块——菜单管理

    从本章开始,主要讲解的是页面中对框架相关功能的调用方法,比如列表页面(又分为有层次感列表和普通列表).编辑页面.多标签页面等,只要熟悉了这些函数的使用方法,那么开发起来就会很便捷了. 1.如图先创建菜 ...

  8. 从零开始编写自己的C#框架(17)——Web层后端首页

    后端首页是管理员登陆后进入的第一个页面,主要是显示当前登陆用户信息.在线人数.菜单树列表.相关功能按键和系统介绍.让管理员能更方便的找到息想要的内容. 根据不同系统的需要,首页会显示不同的内容,比如显 ...

  9. 从零开始编写自己的C#框架(16)——Web层后端父类

    本章节讲述的各个类是后端系统的核心之一,涉及到系统安全验证.操作日志记录.页面与按键权限控制.后端页面功能封装等内容,希望学习本系列的朋友认真查看新增的类与函数,这对以后使用本框架进行开发时非常重要. ...

随机推荐

  1. antd desgin vue 报错 Warning: Each record in table should have a unique `key` prop,or set `rowKey` to an unique primary key.

    警告:表的数据源中的每条记录都应该有一个唯一的“key”道具,或者将表的“rowKey”设置为一个唯一的主键, 只需要添加 :rowKey="record => record.id&q ...

  2. ReactNative出现错误问题'React/RCTAssert.h' file not found

    今天搭建一个rn的项目,项目可以运行但就是报一个错误, 查阅是由于Pods里的React结构改变了,配置没有改过来,所以出现找不到文件的问题 修改search paths 中 header searc ...

  3. python基础学习 day 1

    初学python,记录下自己的历程~ 了解了一下python的基本概念,现在使用的比较多的就是python2.7 学习了if语句和两个经典的循环语句 #关于if语句的应用 name = raw_inp ...

  4. substr()、substring()、slice()

    substr(start,length) start(必选)开始位置的下标 可为负数-1即为倒数第一个字符以此类推 0为第一个字母下标 length长度(可选)如果省略该参数则默认到最后一位 var ...

  5. 【leetcode】1014. Capacity To Ship Packages Within D Days

    题目如下: A conveyor belt has packages that must be shipped from one port to another within D days. The  ...

  6. TortoiseGit操作之提交代码到远程库

    1.在本地代码库的文件夹中,"右键" 2.GIT提交要求必须填写Commit message,请认真填写描述信息. 建议填写的变更项编号,如上图. 代码提交到本地的配置库中,然后p ...

  7. Request Payload 和 Form Data 的区别

    概述 我正在开发的项目前端和后端是完全独立的,通过配置 webpack 的 proxy 将前端请求跨域代理到后台服务.昨天发现,我前端执行 post 请求,后台 springmvc 的 @Reques ...

  8. python每日练习--基础题

    """ 1. 现有面包.热狗.番茄酱.芥末酱以及洋葱,数字显 示有多少种订购组合, 其中面包必订,0 不订,1 订,比如 10000,表示只订购面包 "&quo ...

  9. Java Web学习总结(7)JSP(一)

    一,JSP基础语法 1,JSP模板元素 JSP页面中的HTML内容称之为JSP模版元素. JSP模版元素定义了网页的基本骨架,即定义了页面的结构和外观. 2,JSP脚本片段 JSP脚本片断(scrip ...

  10. MVVM MVC

    在WPF的MVVM模式中,View和ViewModel之间数据和命令的关联都是通过绑定实现的,绑定后View和ViewModel并不产生直接的依赖.具体就是View中出现数据变化时会尝试修改绑定的目标 ...