开源测试工具 App 接口测试工具之 apimock (动态 mock 服务器返回)

zhangzhao_lenovo · 2016年10月31日 · 最后由 天琴圣域 回复于 2017年03月30日 · 9327 次阅读

app 接口 mock server 端返回一工具,一剑在手,跟我走! app 崩溃就靠它了

优势

  1. 动态实时 mock
  2. 命令配置灵活,键值支持精确及正则模糊检索,提供多个 fuzz 功能
  3. 容易理解,方便易用

适用场景

app 对服务端返回的容错测试

服务端返回一旦出错后果很严重,app 轻则 ui 异常重则崩溃,像 api 404,502,长时间无返回,延迟, 返回空 body,键值错,无值,异常值这几类一般必测

项目中使用该工具后效率有了明显提升


原理

  • 如何实时 mock?

app 通过 pc 上 fiddler 代理访问 api

通过运行 proxy xxx 给 mock server 注入 mock 配置,支持重置

借助 fiddlerscript 实现包转发给 mock server,server 根据运行时动态配置对包进行劫持修改,修改后再返还给 fiddler

fiddler.js 重写 OnBeforeResponse 将指定包劫持转发给 mockserver

static function OnBeforeResponse(oSession: Session) {
        if (isautocap && oSession.HostnameIs(filterUrl) && oSession.responseCode == 200) {
            oSession.utilDecodeResponse()
            var rawbody = System.Text.Encoding.UTF8.GetString(oSession.responseBodyBytes);
            var j=Fiddler.WebFormats.JSON.JsonDecode(rawbody)
            if(typeof(j.JSONObject) == "object" && Object.prototype.toString.call(j.JSONObject).toLowerCase() == "[object hashtable]" && !j.JSONObject.length) {
                try {
                    var api = oSession.PathAndQuery.split('?')[0]
                    rawbody = api + ':' + rawbody
                    var mockbody = mock('127.0.0.1', 8390, rawbody)
                    j=Fiddler.WebFormats.JSON.JsonDecode(mockbody)

mockserver.py 执行 mock


def prep(self,data):
    api, data = data.split(':', 1)
    data = json.loads(data, encoding="utf8")
    mock = {}
    data['mock'] = mock
    if api in self.api or not self.api:
        try:
            data = self.mock('', data)
  • 关键字索引及 fuzz

索引找 response 中的关键字,并修改这个项的值

def key(data):
       for k in self.key:
           v= self.value[k]
           if ':' in k:
               str,k = k.split(':',1)
               if str.lower() in ['regex','re','regexp']: data = regexpsearch(data,k,v)    #关键字或正则模糊查找
           else:
               l_key= k.split('.')
               data = exactsearch(data,l_key,v)    #精确查找
       return data

   mockfun = {
              'body': lambda: body(self.body),
              'key': lambda: key(data),
              '': lambda: data,
              'clear':lambda : self.init()}
   return mockfun[self.type]()

修改的值可以是具体值或者 body,也可以是 fuzz 函数 也可自行实现其他 fuzz

def setstrshorten(json, k,v):
    v=str(v)
    json[k] = (len(v) <= 3) and v or v[0:len(v) - random.randint(1, len(v)-1)]

def setstroverlen(json, k,v):
    v=str(v)
    n = 1025
    # n=4294967294  max string ,..dot use!
    for i in range(0, n): v += '1'
    json[k] = v

def setstrillega(json, k,v):
    json[k] = str(v) + ',*&#%()='[random.randint(1, 8) - 1] + 'H1 \u266a@\u5c0f\u8776\u6c42\u5b88\u62a4'

list = {   'del': lambda: delkey(json,k),
           ...
           'none': lambda: set(json,k,None),
           '0': lambda: set(json,k,0),
           '-1': lambda: set(json,k,-1),
           'maxlong': lambda: set(json, k, 9223372036854775808),
           '*n': lambda: setintmultin(json, k,kv),
           'cut': lambda: setstrshorten(json, k, kv),
           'overlen': lambda: setstroverlen(json, k, kv),
           'illega': lambda: setstrillega(json, k, kv),
            ...
           }
return list[type]()
  • 控制台下发 mock 命令

python proxy.py -a /api/user/get -k errno=1 劫持 api 为/api/user/get,原 response 中 errnor 字段修改为 1
python proxy.py -a /api/user/get -k re:name="H1 \u266a@\u5c0f\u8776\u6c42\u5b88\u62a4" 正则索引含 name 的修改为带乱码符号字符串
python proxy.py -r 1 清空 mockserver 中配置
python proxy.py -d 2g 模拟延迟 2g 网络
python proxy.py -b {} 返回空 body
python proxy.py -b {xxx{'' 返回非 json 格式 body
python proxy.py -a /api/user/get -k re:name=fun:del 模糊查找 name 关键字,将其键删除
python proxy.py -a /api/user/get -k data.uid=fun:maxint path 查找 data 节点下 uid 关键字 ,修改为溢出的 int

是不是很容易理解


未来

考虑后续与思寒的 appcrawler 做插件对接,做自动遍历的 mock 测试


开源地址

https://github.com/zhangzhao4444/Apimock

如有 bug 和好想法可及时联系我

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 33 条回复 时间 点赞

好高产~

已经 star

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

下次沙龙来分享下吧.

问下这类测试,在你们测试中优先度很高吗

楼主棒棒的,刚好后续可以关注下容错性测试,楼主 360 的童鞋吗

#4 楼 @pacerron 很高吧 接口的业务逻辑正确后,就要尽量测试容错。服务器一旦崩溃挂起无响应至少要保证 app 无崩溃。以前出过一个案例,流量过大导致接口服务器崩溃了,app 启动连弹幕长连接长时间无响应于是 app 崩溃,app 上传崩溃日志后自重启,如此循环,服务器重启后又因不断重连再次崩溃。

#5 楼 联想?

不错的落地方式,可以搞个 mock 平台,平台上直接 hook response 的,展示后端返回数据,用户可以可视化的修改 response,再返回。 更屌啦

不太懂 python 勉强看了下 git 的代码,是否可以再向上封装一层,一个域名定制一些 mock 方式(不用每次手工去输入),封装成一些类。然后把这些定制持续集成起来,最终实现共产主义

#11 楼 @jet 忘了吹捧了。 这玩意儿真屌,想象了一下在生产中按照自己的业务情况稍微扩展下代码,可极大提高效率。

#11 楼 @jet 目标是做一个轻量化的工具,使用方便,简单即可。
现在是已经实现了一些 mock ,但具体跑哪种 mock 目前还需要人工定义。
最初考虑的以命令注入 mockserver 就是为了可以替代人工定义的来实现全自动,正在考虑如何做个能遍历 api 的 mock 测试,需要有个能不断触发 api 的入口

#10 楼 @dengwei729 平台化太重了需要兼顾很多东西,其实 mockserver 就已经是个后端了

#13 楼 @zhangzhao_lenovo 如果要简单,个人认为就用 json 文件对应一下特定 api 和 mock 就行了, 类似这种

[{
  "apiName":"damnson",
  "baseLink" :"xxx",
  ...
  ...
  "mocks" : [mock1, mock2]
},
{
    ......
}
]

如果不增删改 mock 逻辑,核心代码不用动,就维护这个 json 就可以了。

#13 楼 @zhangzhao_lenovo 另外 fiddler 里面劫持域名也可以写到配置里,这样方便不同域名的接口容错测试

#17 楼 @jet 恩 fiddler 里劫持域名原本是设计在 server 里的,但我测试发现 fiddler 每个包都会转发给 server,这样 server 负担太重了 可能会有效率问题

#16 楼 @jet 每个 api 一个 json 吗?那得维护很多个 json
另外何时触发 mock1 何时触发 mock2 呢?
之前我也考虑过这个问题

#18 楼 @zhangzhao_lenovo 恩。不过如果你要和某种 crawler 结合,那必须避免所有的手工操作,否则意义就不大了。我觉得可以以一个接口的 domain 为单位执行一次完整的测试。比如 domain A,下面的事情用脚本全部完成

配置文件的格式可以为(一个域一个 json)

{
    domain : "A",
    apiList : [
        {
            "apiName" : "api1"
            "mock 以及和app操作相关的等其他参数等"
        },
        {
            "apiName" : "api2"
            "mock 以及和app操作相关的等其他参数等"
        },
        ...
    ]
}

#1 读取配置文件,domain 为 A
#2 fiddler.js 动态重写,将 domain 修改为 A,自动启动一次 fiddler,这样 mockserver 压力就不大了
#3 用一个入口文件,开始遍历本 json 的 apiList。 这个怎么做要看你和什么 crawler 结合,只要在 json 里面提供必要的参数就行
#4 做验证,结果监控输出等。最后要关闭所有资源 比如 fiddler. 一次 cycle 就算完成
#5 可以用类 jenkins 简易做下持续集成(如果有必要的话)

核心的东西还是你那一套。。 不知道有没有道理。。

#19 楼 @zhangzhao_lenovo 我觉得主要还是你想让他最终是个什么东西。 如果只是半手工这样提高一下效率,那目前这样再弄一下报告也就可以了,把一些常用的 cmd 或者 sh 命令维护一下,已经很不错了。 如果要结合 crawler,那只要具体考虑到这个 crawler 是怎么遍历 app 的,何时触发 mock1 mock2 就全写在 json 里,自己约定一种规则就可以了。。 这些都是个人愚见。。

#21 楼 @jet 恩 和 crawler 结合应该是可行的

#22 楼 @zhangzhao_lenovo 由于我不熟悉 python,我想用你这个思路换个语言山寨一套,后面的我就按我这边的需求去扩展,求恩准。

#23 楼 @jet 没问题啊

楼主这种方式实现不错,但是存在一定的使用成本,依赖用户的环境
可以考虑用 anyproxy 做代理,利用前后端技术提供可视化编辑界面做一个 Mock 平台,动态绑定 Rule 及 Mock 数据可能会更加人性化。😀

#26 楼 @fresh 平台就算了,本是个极简单的功能就应该更简单化。
anyproxy 之前有看过,实现动态的话也得另有个 mockserver,修改 rule_xx.js 与 mockserver 通信来交换数据,实际本质上和用 fidder 是一样的。 最后考虑稳定还是选了 fiddler

zhangzhao_lenovo [该话题已被删除] 中提及了此贴 11月16日 15:02

很机智,不过我一般写东西,都是直接 spring-boot 返回 json ,眼界大开

#29 楼 @zasdsd 这个原理还是相对简单,其实就是个中间件。 spring-boot 不是很懂哈

#31 楼 @zhangzhao_lenovo
今天使用了楼主的软件,有几点疑问,想当面和楼主交流下。不知道楼主能给一个 qq 否。
另外今天在使用软件的时候发现了一个小 bug,可能楼主当初设置好了参数以后忘记改了。

根据上面我的截图,只要 json 的返回大小超过 16384,那么就不能拿到完整的 json,就算我们做好了 monk 的准备,程序也会报错。

建议可以把 github 上的代码修改下,把这个接受值调大一点。事实证明,16384 的容量可能不够用。

PS:python2 和 python3 写法上真的有很多区别。不过在 debug 这个 bug 的同时,学到了很多东西,很感谢。

#32 楼 @lunamagic 多谢提醒,确实有此问题。之前为啥是 16384 已经记不清了。 qq77227005

dengwei729 回复

哈哈,咱还真有做。不过在想怎么写文档,因为配置起来稍麻烦。。。

抓包动态 mock 好有意思呀,fork 看看,不过不懂 Python 还得现学,,,😔

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