为功能测试构建通用mock server系统

发表于:2013-11-26来源:InfoQ作者:余昭辉点击数: 标签:mock server
为功能测试构建通用mock server系统.mock在单元测试中已经众所周知。现今我们有各种功能强大而又好用的mock框架,可以很方便的解除单元测试中各种依赖,这大大的降低了编写单元测试的难度。而测试驱动开发(TDD)更进一步将mock作为一种设计手段,来辅助识别出元素之间交

  mock在单元测试中已经众所周知。现今我们有各种功能强大而又好用的mock框架,可以很方便的解除单元测试中各种依赖,这大大的降低了编写单元测试的难度。而测试驱动开发(TDD)更进一步将mock作为一种设计手段,来辅助识别出元素之间交互的接口和职责。

  那么在功能测试(这里提到的功能测试指的是用户级测试)这个层次,是否有必要使用mock呢?如果有必要又将如何构建呢?或者说是否有可能像单元测试中那样构建一个通用的mock server系统呢?本文将根据我的实践经历,向大家介绍一个通用mock server系统的主要组成部分以及设计思路。

  Why

  现今的业务系统很少孤立存在,它们或多或少需要使用兄弟团队或是其他公司提供的服务,这为我们的联调和测试造成了麻烦。对于这种情况,我们常见的解决方案是搭建一个临时的server,模拟那些服务,提供数据进行联调和测试。这就是mock server的雏形。一般来讲,搭建这种mock server系统比较简单,不过它的功能也比较简单,而且往往需要针对不同的接口重复开发。那有没有可能像单元测试中使用的mock框架那样构建一个通用的mock server系统呢?

  How

  观察单元测试中的mock框架,我们会发现一般使用mock的流程是:

  init mock //创建mock对象

  config mock //设置mock期望

  setup mock //将mock对象设置给被测对象

  call //调用被测接口,被测接口里的代码会调用mock对象

  verify //验证

  拿mockito举例:

  User expected = new User(“admin”, “12345”);

  //init

  UserDAO dao = mock(UserDAO.class);

  //config

  when(dao.findByName(“admin”)).thenReturn(expected);

  //setup

  UserService service = new UserService(dao);

  //call

  User actual = service.login(“admin”, “12345”);

  //verify or assert

  借鉴这种做法,我么就可以构建一个简单的mock server系统,接下来的内容中,我们会在这个mock server的基础上演化出比较完善的版本。

  相关厂商内容

  京东“宙斯杯”创新应用大赛开始了,100万奖金等你拿哦!

  版本1(简单的模拟值)

  假设我们需要mock的是HTTP接口。我们的mock server提供一个配置接口(对应着上面的config mock步骤),测试运行之前调用配置接口将需要mock的HTTP接口URL以及需要返回的值传递过去,mock server内部建立url 到返回值的关联(这里就类似存在一个哈希表一样)。Mock server还提供另外一个接口供被测系统调用。这是一个通用的接口,所有原先指向真实服务的地址全部被指向到该接口(可以通过修改配置或修改系统 hosts文件)。当该接口被调用时会寻找刚才建立的url 到返回值的关联,并将mock的值返回。这样一个非常简单的mock server就构建出来了,对于一些简单的联调场景基本够用。这个时候我们的mock server的原理图如下面所示:

  版本2(提供调用参数的查询)

  有了第一版的mock server,对一些只需要模拟值的场景是够用了。但是,mock的作用仅限于此么?想想单元测试中的mock。在单元测试中mock框架除了能够为被测系统构建输入值外,还能捕获到被测程序传出的值,然后对这些值进行校验。举一个实际的例子:在我们的系统中经常需要向用户发送短信,为了对发送的短信功能以及短信内容进行验证,在没有mock之前我们可能真的需要向真实手机发送短信,然后验证。这不仅降低了测试效率,也增加了不少不可控因素。为此我给 mock server添加第三个接口:query接口。在被测系统调用mock接口时,mock会记录下被测系统传递给mock接口的参数值,然后测试中可以调用 query接口查询到记录的值,在测试中可以对其断言,而且这里记录下的调用记录还可以作为日志提供出来,提高系统的诊断能力。这样我们的mock server的结构就变成:

  版本3(可以根据参数模拟)

  现在我们的mock server已经可以提供类似verify的功能了,但实际上它还不能算一个真正的mock。它也不能处理哪怕稍微复杂一点的情况。在实际中,我们经常需要针对不同的参数返回不同的值。举个简单的例子:我们mock一个支付接口,对于订单号123我们期望支付成功,对于订单234期望支付失败。这就引入了我的mock server的两个核心组件:extractor和matcher。Extractor组件主要用于从调用的参数上提取出参数值,然后转换成 key/value的格式提供给后续的环节使用;因为请求的参数格式多种多样(json,xml等),extractor 为此提供了统一的接口。

  Matcher组件的作用是在拿到extractor传递过来的key/value值后利用一些匹配器匹配到具体设置的期望上,所有匹配到的调用会返回对应的值。也就是说mock server内部不再是简单的映射了。后面再介绍DSL部分的时候会介绍matcher使用的语法。这样我们的mock server结构就演化成下图:

  版本4(提供多种协议的支持)

  估计有人在抱怨,说了这么多这个mock server还只能mock HTTP接口啊,我们的系统中存在HTTP接口,RPC接口,SMTP接口等等。这是mock server中协议组件的职责。协议组件是mock server的入口,它提供多种协议的服务,并且解析出协议包数据,然后将数据交给extractor组件;除此之外,协议组件在收到上层的返回值后,会按照协议的格式返回给被测系统 。利用一些开源的类库,我们可以很容易对一些通用协议提供支持,但对一些私有的二进制协议如果没有现成的库支持,要重新开发成本很大,不过我们可以从客户端来解决这个问题,这在后续的文章中会有介绍。

原文转自:http://www.infoq.com/cn/articles/auto-test-mock-server?utm_source=infoq&utm_medium=related_content_link&utm_campaign=relatedContent_articles_clk