前端测试 基于 Anyproxy 的 Mock 服务器设计优化升级

terrychow · 2017年04月28日 · 最后由 文若 回复于 2019年12月10日 · 4096 次阅读

一、前言

  • 最近陆陆续续地有 testerhome 中的朋友加我微信向我请教一些测试技术方案的实现,问得比较多的是 mock 服务器的实现,就是菜鸟搭建 Mock 服务器实践:Anyproxy+Moco,果然这是一个菜鸟搭建的东西,后面请教我的那些朋友都遇到了大坑,我先和所有看之前那套方案的朋友说声对不起,也应了我今年最喜欢说的那句话,去年啥都做,但啥都不精,好吧,不浪费篇幅,今天就特意应一些朋友的请求,也为了统一解答大家的一些疑点,我把后面我经过 5 个版本迭代的 mock 服务器分享给大家,希望对大家能起到那么一丢丢的帮助

二、之前的坑

  • 看过上面那篇文的朋友都能了解到,之前的 mock 服务器主要分为 anyproxy 和 moco,后面发现了一系列的严重问题:

    1.moco 中文编码处理很麻烦,而且 moco 起的作用是数据管理部分,那数据管理的话没必要用一个服务器那么重
    2.mock 的 api 写死在代码里面,要改 mock 的 api 时需要改代码和重启服务器,而且其实每次只能 mock 一个 api
    3.如果在测试过程中不需要 mock 时,就必须关掉服务器,那又要去设置一次代理,很麻烦
    4.api 多了之后是否会有性能问题

好,那我的工作又来的,后面就通过 5 个版本的迭代,把上面的 4 个坑感觉还是很好的填了的

三、优化过程

  • 优先是对整个 mock 服务器的架构设计,直接了白

    1、api:管理需要 mock 的接口,可以直接用 txt 来按行管理

    2、data:用于存放要 mock 的数据,可以和 api 一一对应,以 json 文件格式来管理,数据内容是整个 json

    3、conf:用于管理配置文件,目前最主重要的一个配置就是 mock 的开关

还有 tmp 是放在一些临时文件,好,上面的架构基本把接口和 mock 数据从 mock 服务器的代码中分离出来,那接下来就是主核心的 mock 服务器代码

具体的实现代码:

rule_mock.js
global.api = "testapi";   //mock接口标识
module.exports = {

    summary:function(){
        return "replace response data by local response now";
    },

    //mark if use local response
    shouldUseLocalResponse : function(req,reqBody){
      var fs=require('fs')
      var msdata=fs.readFileSync('./conf/ms.json',"UTF-8"); //读取mock开关配置文件
      var ms = JSON.parse(msdata);
      var isOpen = ms['isOpen'];
      if (isOpen==0) {                               //当isOpen为0时,把直接返回false跳过所有mock
        console.log("isOpen is :" +isOpen+",skip mock!");
        return false;
      };
      var file = fs.readFileSync('./api/mock_api.txt',"utf8");  //读取api文件
      var apilist = [];
      apilist=file.split(/\r?\n/ig);            //按行分割存为列表
      for (var i = 0;  i < apilist.length; i++) {    //循环mock,有多少个接口就mock多少次,支持多接口同时mock
        var mockapi=apilist[i];
        if(new RegExp(mockapi).test(req.url)){     //匹配是否为被mock接口
          global.api=mockapi
          return true;
          }
        };
      return false;  
    },

    dealLocalResponse : function(req,reqBody,callback){
            var fs=require('fs');
            console.log('mock api is: '+global.api);//使用global对象访问到"全局"变量
            fs.readFile('./data/mock_data.json',function(err,data){  //读取mock的数据,data为一个json对象
                if(err)  
                    throw err;     
                var arr = JSON.parse(data);
                try{
                  var respon=arr[global.api];  //取出json对象中对应api的数据
                  console.log("status  :" + respon.status);
                  console.log("headers  :" + JSON.stringify(respon.headers));
                  console.log("body  :" + JSON.stringify(respon.body));
                  var newDataStr=JSON.stringify(respon.body);
                  callback(respon.status,respon.headers,newDataStr); //组合成一个全新的响应对象并返回
                }

                catch(e){
                  console.log(e);
                  console.log("you should go to set mock data for api: "+global.api);  //作异常处理,当被mock的接口没有对应的mock数据时,提示并防止服务器退出
                }
            })

    }
};

通过注释来看设计思路就很清晰了,代码里面最核心的两个参数 global.api 和 isOpen,isOpen 用于控制是否要 mock,global.api 就是被 mock 接口和对应 mock 数据的一个关联,通过上面的方式,后面就不再需要通过 moco 来管理 response,直接组合就能解决问题了,通过上面的设计思路,能够解决我上面所提到的坑的前 3 点,好,具体操作来看一下

四,操作演示

  • 通过 anyproxy 来启动服务器执行 mock 脚本和在浏览器设置代理的方法上面文章已经有了,这里就不再演示了,直接验收 mock 过程 1、测试通过 mock 发送消息和对讨论设为关注的接口:cim.msg%3Areply 和 setFlag(接口为已缩写)

以及测试的数据:

{,
"cim.msg%3Areply":
    {"status" : 202,"headers" :{"content-type" : " text/x-json;charset=UTF-8"},"body": {"code":"FA_OVERFLOW","var":{}}},
"setFlag":
    {"status" : 202,"headers" :{"content-type" : " text/x-json;charset=UTF-8"},"body": {"code":"FA_FORBIDDEN","var":{}}}
} 
  • 目前的 mock 开始 isOpen 设置为 0,执行 mock ----------
  • 可以从上面看到,由于 mock 开关已关闭,所有请求都会跳过 ----------
  • 当 isOpen 不为 0 时,开启 mock ----------
  • 可以看到 mock 已生效 ----------
  • 同时可以连续 mock 多个接口,还可以随意增减改需要 mock 的接口,无需重启服务器 ----------
  • 如未对 mock 的 api 设置对应的 mock 数据 ----------
  • 就会有对应的提示,所以不用重启服务器,可以随时修改 mock 数据和 api

所以大致的操作演示就是这样,其实还有第 4 点没说,当被 mock 的接口数到达一定数量时,mock 是否会有性能问题,因为总是整个文件的读,文件量大了,肯定会有问题的,所以就可以通过具体的业务模块的接口来分布到不同的文件中管理,逻辑如下:

分割方法:

rule_mock.dev.js
global.apigroup="testapigroup";

//省略部分已展示过的代码,具体实现逻辑和结合自己项目本身来实现,这个实现方式是我们产品的,未必通用

      try{
        //console.log("req.url is :" +req.url);
        var res=req.url.split('json?func=')[1];
        var useapi=res.split('&sid=')[0];
        //console.log("useapi is :" +useapi);
        global.apigroup=useapi.split('%3A')[0];
        console.log("apigroup is :" +global.apigroup);
      }
      catch(e){
        console.log("api pass");
      }

结合我们论客的接口设计特征,取出业务模块,比如 global.apigroup=cim.msg,然后之后根据 global.apigroup 的值读取对应模块的文件,比如读取 cim.msg.txt,同时加上异常处理

rule_mock.dev.js

//省略部分已展示过的代码
       try{
              var datapath='./data/'+global.apigroup+'.json';
              var filetmp = fs.readFileSync(datapath,"utf8");
            }
            catch(e){
              console.log('mock_data_file"'+global.apigroup+'" is not exist! skip to mock_data');
              var datapath='./data/mock_data.json';
            }

所有最后实现的效果,这样就能优化 mock 服务器的性能,可以看到明确的模块分组

同时对于未模块的 mock 接口,可以直接用回 mock_api 里面的数据,同时会跳过

就这样,我前面所说的 4 个坑就基本填完了

五,最后几句

  • 之前和大家说过的终于写出来了,技术方案要不断地改良,结合业务来实现,才能体现出技术方案的价值,同时也对个人能力的提高起很多的作用,我现在还继续在为我去年的一堆技术方案还债,同时开始着手学习如何制定测试策略,因为没有策略的测试感觉就是瞎测,去年一年没把东西做好做精,很多是由于没有制定出测试策略来指导测试工作的开展,所以也欢迎大家继续共同讨论,共同学习,共同进步,我个人也是很喜欢开分享会的,毕竟分享会最大的受益人是演讲者,这种方式对自己的提升也很快,好吧,有空我可以分享一些作为一名 90 后工作不到 2 年的测试新人是如何学习测试技术和在职场中成长的一些经验,反正就是大家都多聊聊,多点分享,才会进步,谢谢啦
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 12 条回复 时间 点赞

再给大家开条路,anyproxy 是一个平台,对比 fiddler 和 charles 会更加的通用,我们团队几个人也就只要启动一台 anyproxy 就够了,还可以在 linux 上玩,那意味着可以放到 docker,用 docker 可以同时启动多个代理服务器来分别 mock,而且管理也很方便,不妨试试

之前收藏了帖子,放假刚好找到学习下

—— 来自 TesterHome 官方 安卓客户端

雪怪 回复

有位朋友和我说目前支持 json 格式的报文,想加入动态支持 xml 的,朋友您也可以考虑一些优化😁

"2.mock 的 api 写死在代码里面,要改 mock 的 api 时需要改代码和重启服务器,而且其实每次只能 mock 一个 api", 关于这一点,我看你前一篇帖子是提到修改了 moco 已经 load 的 json, moco 就会重新加载,这不就不用重启了么,而且 api 也是写在 json 里没有写在代码里啊,所以这一点不是很理解,可以再解释一下吗?

特特兔 回复

是选择的 api 和选择 mock 数据写死在了 rule_mock.js 里面了,每次都只能拿到同一个 mock 数据,理解一下上一篇的逻辑应该就能理解了

terrychow 回复

嗯,前面看的不仔细,以为你说的这点是 moco

anyproxy 使用 http 层,我们这边 网络层通过 jni 实现的,是否有解决之道?

林家木子 回复

JNI 这种本来 anyproxy 就不支持吧,如果需要做 jni 的 mock,还不如直接在代码上对方法或 api 做,而且最好理解一下 stub 桩服务器和 mock 程序的区别吧,我也是最近才理解区分 stub 和 mock

向阳 用 anyproxy 记录移动端 UI 测试数据 中提及了此贴 09月27日 20:26

666 学习了

学习了,请问关于 response 的模拟,这种该如何实现:POST 请求是 json 报文,其中有一个字段是 ID,response 需要获取这个 ID 作为响应 json 的一部分。

正想搞数据分离的 mock,看了这个有些启发。谢谢

(node:36878) UnhandledPromiseRejectionWarning: TypeError: You may only yield a function, promise, generator, array, or object, but the following object was passed: "replace response data by local response now",您好,启动的时候会报这个警告,但可以启动服务成功,不过 moco 不到指定的 json,啥问题呀,百度了半天没解决

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册