移动性能测试 在 anyproxy 上做 mock 和 fuzz 测试

fenfenzhong · 2016年11月04日 · 最后由 Cheney 回复于 2017年01月03日 · 2987 次阅读
本帖已被设为精华帖!

最近社区用 anyproxy 的挺多,我再来凑一波热闹

引言

写这个工具,主要有几个原因:

  1. 最近老大在尝试不同视角的测试 ---- 健壮性测试,任务下来,所以挽起袖子就开撸了
  2. app 很可能因为后端 api 做了变更,返回了一个异常的值而出现难以预知的问题,健壮性受到碰撞,所以这种测试是有实际价值的
  3. 思寒的一篇帖子基于 fuzz 技术验证移动端 app 的健壮性我觉得挺好玩,这里要非常非常感谢他提供的思路,从下面的回复也可看出来大家还是比较感兴趣,而他因为遇到一些问题尚未开源,所以我挽起袖子就开撸了
  4. 社区里最近用 anyproxy 的挺多,我想说我们厂应该是最早开始用 anyproxy 的,流量测试中也使用到了这个工具。现在大家都开始定制,我表示不服,所以挽起袖子就开撸了
  5. 最近有幸被拉到了社区的技术专家群,趁着分红前多发一篇吧,哈哈

0x00

关于健壮性测试,模糊测试的一些描述,我希望大家可以去思寒的帖子里看,我也是从他那里出发的,我想做的东西,和他帖子里讲到的差不多,不过我又加上了另外的一些规则,总的来说,第一阶段我总结了下需求,大致如下:

  1. 只支持 json 结果的 mock 和 fuzz
  2. 该走线上的还走线上,可针对某个具体 api 做修改
  3. 能够记录原始的 request 和 response
  4. 在 json 结果中,能够指定某个 key or value 进行增、删、改
  5. fuzzy 化,根据原值随机返回 fuzz 值
  6. 轻量级 ,方便用于 CI,自动化测试,而并非动辄就写个系统
  7. 跨平台

0x01

调研阶段,为了实现第一阶段的需求,并且结合公司的技术栈(python),主要是搜 github 和 google,排除了下面这些:

  1. whistle ,太复杂
  2. rap,GUI 界面很贴心,但是主要是为了方便前端调试,生成模板,适合后端服务还没有起来的情况
  3. mock-server,虽然 python 很贴合,文件保存这点做的好,但是不太方便做 mock 和 fuzz
  4. moco,东西很好,也有点重,java 栈,之前社区有同学也做过 moco + anyproxy 的分享

所以,最后选用的技术栈和工具是:

  1. nodejs
  2. anyproxy
  3. jsonpath
  4. mockjs

可以方便的模块化,打包成命令行工具,只有一个字,轻

0x02

因为这是一个依附于 anyproxy 的 mock 工具,所以 anyproxy 的 rule_file 这部分是一定要研究研究的,文档也很清楚了,我说说我踩过的坑:

  1. 截取 https 的请求,并不是在 anyproxy 的启动 options 里把 type 设置为 ‘https’,而是把根据 rule_file 里的shouldInterceptHttpsReq
  2. 做值的替换,肯定要用到replaceServerResDataAsync,这个方法的 serverResData 参数,是一个 Buffer 对象
  3. 在用 jsonpath 定位后,因为我的需求里面是有 api 级别的 mock 和 global 级别的 mock,也有两者都有的(api 会覆盖 global),这在怎么区分命令行参数,以及对类似 $..name 这样的值做 mock 或 fuzz 时,增加了一定难度

已有功能:

usage: anymocker [-h] [-v] [-s SAVE] [-p PORT] [-m [MOCK [MOCK ...]]]
                 [-a [API [API ...]]] [-i [INJECT [INJECT ...]]]
                 [-d [DELETE [DELETE ...]]]


anymocker usage

Optional arguments:
  -h, --help            Show this help message and exit.
  -v, --version         Show program's version number and exit.
  -s SAVE, --save SAVE  file save path
  -p PORT, --port PORT  proxy port
  -m [MOCK [MOCK ...]], --mock [MOCK [MOCK ...]]
                        mock value
  -a [API [API ...]], --api [API [API ...]]
                        specify url
  -i [INJECT [INJECT ...]], --inject [INJECT [INJECT ...]]
                        inject field
  -d [DELETE [DELETE ...]], --delete [DELETE [DELETE ...]]
                        delete field
  • -s 参数是指定原始的 request 和 response 的保存路径
  • -p 指定 anyproxy 启动端口
  • -a 指定具体 api,可以只针对这个 api 的返回数据做 mock
  • -m 后面跟 jsonpath 表达式和值,比如 $..name=fenfenzhong,即可把返回的结果里,所有 name 属性都改为 fenfenzhong
  • -i 后面跟 jsonpath 表达式和值,可以在返回数据里置入你想要的值,对于一些由字段控制的 UI 特别有效
  • -d 后面跟 jsonpath 表达式,比如 $..title,可以把返回结果里的所有 title 属性都删除

有了-i(增),-d(删),-m(改),可适应的需求范围就比较广了

0x03

看看效果
原图是:

全局mock,把所有name都换成fenfenzhong,所有text都换成66666,所有title都换成lgtm
$ anymocker -s save -m $..name=fenfenzhong $..text=66666 $..title=lgtm

解析之后,mock 规则长这样:

{
  "global": {
    "mock": [
      "$..name=fenfenzhong",
      "$..text=66666",
      "$..title=lgtm"
    ]
  },
  "api": {}
}

效果图是:

//全局mock,把所有text都换成66666,所有title都换成lgtm,删除所有的name属性,并且植入一个属性 qa=douban
// 本来想删除所有的title 属性的,结果。。崩溃啦
$ anymocker -s save -m $..text=66666 $..title=lgtm -i $.qa=douban -d $..name

解析之后,mock 规则长这样:

{
  "global": {
    "mock": [
      "$..text=66666",
      "$..title=lgtm"
    ],
    "inject": [
      "$.qa=douban"
    ],
    "delete": [
      "$..name"
    ]
  },
  "api": {}
}

效果图是:

摘取某一个返回的 json:

当既指定了 api,又有全局规则的时候,会先应用全局的规则,然后再应用 api 的规则,当两者涉及到的属性名一样时,api 的会覆盖全局的。如果指定了 api 但是却没有命中,则只应用全局的,或者直接返回原始值

//全局mock,把所有text都换成66666,所有title都换成lgtm,所有name都换成fenfenzhong,再针对具体的/api/v2/note ,把text换成sixsixsix,再增加一个属性qa=douban
$ anymocker -a /api/v2/note/ -i $..qa=douban -d  -m $..text=sixsixsix  -m $..name=fenfenzhong $..text=66666 $..title=lgtm

解析之后,mock 规则长这样:

{
  "global": {
    "mock": [
      "$..name=fenfenzhong",
      "$..text=66666",
      "$..title=lgtm"
    ]
  },
  "api": {
    "/api/v2/note/": {
      "mock": [
        "$..text=sixsixsix"
      ],
      "inject": [
        "$..qa=douban"
      ],
      "delete": []
    }
  }
}

全局 mock 的效果图,和之前一样
但是一个具体的日记条目里,可以看到返回的值里已经多了一个 qa=douban,mock 后的 text 值全变为了 sixsixsix:

0x03

上述的几种方法可以适用于值的修改,但是有的时候需要根据原始值来进行 fuzzy 化处理再返回,也是可以做到的,需要把 jsonpath 的值指定为 FUZZ,fuzzy 化的规则主要有以下几个:

  1. number,string,和 boolean 类型的才能被 fuzzy 化
  2. 每种类型都有一定几率返回 null
  3. 数字会随机放大缩小倍数,string 会随机变为空串 ‘’
//全局mock,把text,title,name都模糊处理
$ anymocker -m $..title=FUZZ $..name=FUZZ

效果图:

可见第一个 “小脚太太和” 的 title 被随机减去了一些字符,name 返回了空;第二个 name“萌宠” 前后都加了随机字符,title 返回了空,第三个 “人文” 的 name 和 title 前都加了随机字符

最后

由上述的实验看来,工具的完成情况还不错,对于 mock 和 fuzz 都能做到,我举得栗子比较浅,但实际上它可以构造出很复杂的规则,测试数据和该走线上的可以很好的分离,而且由于是轻量级命令行的(就是个 node 模块),跨平台也能使用,也可以很方便的集成到 CI。我自己对这种类型的测试还有一点别的思考:

  1. 这种类型的测试,主要是用随机的值,或者人为构造的极限值去挑战 app,在一定程度上,能够检测 app 的健壮性
  2. 外部服务的失效往往会对重依赖的 app 产生比较大影响,拿到 null 值的可能性增加
  3. 这类问题的优先级可能不是那么高,怎样去构造一个现实可能出现的异常数据(如果真实不可能出现,那即使崩溃了其实也说明不了什么),对测试人员能力有较高要求
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 34 条回复 时间 点赞

anyproxy 可挖掘的潜力很大,前后端测试打通能颠覆自动化测试的模式

思寒_seveniruby 将本帖设为了精华贴 11月06日 07:54

加精理由:创新性的增加了增删动作 增强了应用场景

感谢分享!

#1 楼 @quqing 东西是不错

思路蛮不错的,通过 jsonpath 这种方式去定位元素以及操作。其实应该还可以支持直接写 mock 规则文件吧

#7 楼 @tcat 是的,这个 feature 也正在考虑中啦

我们也用这个做 sdk 的 response mock,如你最后一点所说,基于 fuzz 的测试很多异常属于非正常 case,在梳理结果的时候会比较麻烦。

#9 楼 @simple 恩,是的,构造测试数据更有难度

#10 楼 @fenfenzhong 惨的是我们公司最近刚加了一个证书认证, 我用的 bmp sdk 没法用了. 只好更换新的 sdk. 一直没顾上重新开发出来. 不知道 anyproxy 对 ssl 的证书是如何处理的. 是伪造, 还是复用的基础上伪造.

#11 楼 @seveniruby anyproxy 是『自制根证书,信任根证书后再用它签发各个域名的二级证书,二级证书可以重新对各个页面进行解析』--来自anyproxy 文档

赞,学习学习

思路很新颖。。

感谢分享

能开源出来体验下不?

#16 楼 @lihe1986 可以啊,我们先试用一段时间,如果没问题可以开源

fuzz 咋搞的

#18 楼 @cjtcwyk 目前只做了针对 string 和 number 的 fuzz,是根据原始值来判断是否命中上述两种类型,如果命中,再执行随机的一套规则 -- 对于 string 来说,就是任意增加/缩短长度,也可能为空串'';对于 number 来说,是任意扩大,缩小一定倍数;而且两种类型都有一定概率为 null,概率可以随时调整

#19 楼 @fenfenzhong 最近在弄差不多的东西,想了解下楼主是怎么实现 mock inject delete 这 3 个方法的

请问怎么获得 anyproxy 的 rule.js 文件中 req,option,reqBody 这些变量都有那些方法呢,看 anyproxy 官网上只给了几个例子想定制自己的 rule.js 就难了。还请做过的赐教。PS:本人不会 JS,难道要精通 JS 才能写出来?

#21 楼 @sunflower 不啊,我也不是专业写 JS,只会实现简单需求。我帖子里也说了,公司用的是 python 栈,所以我首选用 python,但一番调研过后觉得现有的代理很多都基于 node 写的,轻量且容易集成到 CI,所以看着学喽,现在的搜索引擎、技术发展这么快,『一个东西能不能被造出来』 早就不是什么问题了,最重要的是它该不该被造出来 :) 我其实觉得 anyproxy 暴露出来的接口,你实现了那些回调就已经可以定制非常多功能啦。至于你说的那些变量有哪些方法,我暂且理解为你在问这个对象有哪些属性,有很多方法,1.你可以自己去 github 看看源码, 2.看你的需求是什么,你完全可以 console.log(paremeter) 把这个对象打出来 ,3. 可以直接问阿里的工程师,但不太建议

#20 楼 @azdbaaaaaa jsonpath 定位 path,自己构造的规则去往这个 path 作相应的值操作,global 的规则先行,api 的再覆盖

#23 楼 @fenfenzhong global local 的 api mock 都做完了,只是 insert 和 delete 的时候,对应的 jsonpath 没有直接提供方法,我只能拿到 jsonpath 路径的一个 list,还要自己写方法根据 jsonpath 路径做增加和删除,感觉自己写的方法比较坑,各种拼接出来的,所以想请教一下 insert delete 的具体的代码,参考一下

#24 楼 @azdbaaaaaa 我刚刚把代码提到 github 了,有兴趣可以去看看,https://github.com/fenfenzhong92/anymocker/blob/master/gen.js ,因为这本身就是基于 anyproxy 的一个 toy 程序,谈不上开不开源,互相学习而已,我里面还包含了挺多无用的打印信息,望见谅。。。如果有什么问题可以随时再找我

#22 楼 @fenfenzhong 多谢提供的代码,现在有些技术点还没搞懂,源码拿走学习了,谢谢

#25 楼 @fenfenzhong 多谢啦,我回去研究一下

#22 楼 @fenfenzhong 我们的请求大部分是 post 请求,我想根据 post 请求里的参数决定是否 mock 返回值,但是打印出来的 req、Body 都是乱码的。看源代码里应该是已经转过码了的 reqData = Buffer.concat(postData); 还望指教怎么才能拿到 post 请求的参数呢

#28 楼 @sunflower 对,这里的数据类型是 Buffer 的,我在帖子里已经提到过了,在 node.js 里,Buffer 可以和 String 互相转换,var req_string = req_buffer.toString('utf-8') 或者
var req_buffer = new Buffer(req_string,'utf-8')

#29 楼 @fenfenzhong 已经使用了 reqBody.toString("utf-8"), 但读出来的还是乱码�r�
��w�#�㯮�
�ˑC%p�kLO�-�ۧ�JS��T��J?ץ�j��P-�"��K��W�c]<;�d�cb�L���V�/0�aH�F�6rZ�g�\���S3��IɉuA�%����}�����􀢉�"���G�9!4O}
�g��0�'�J"�X2t��V6���&��#4E�#w2��g������H�A���W>e4ILQ]���?���!
��!#gj�'6�I�����r&G>�vX�J���V�!FP:>� ��&8�0V�u���:���� �Ew[�fԛ�@��tG��MM-5^Gwi�у�M��w����e^��r�QO4v�u�.N~�[�xI�L
�+p�E�r���t���b0ɝ���O��_D?����̶��] srվ��5����Ѕ<qeg�`���8
m@R�W9����<b�

#30 楼 @sunflower 你先用原生的 anyproxy 装一遍证书,如果已经装了的话重新装一遍,装好之后再起 anymocker,看看?

下载后试用了几下 非常方便

不好意思又来麻烦你了。我想参照你的思路写自己的代理服务器。但是用 jsonpath 解析的时候修改接口返回值 jp.value(respData, key, value), 但打印出来的 respData 的值是 [Object],不是具体的 json 数据,不知你有没有遇到过?想了解更多是否只能看源码了,网上关于具体用法真是少之又少。

#33 楼 @sunflower 如果确定是一个 json 对象,可以试试 JSON.stringify(respData),然后输出看看结构

#30 楼 @sunflower 数据应该是被压缩了,通过 anyproxy 的网页版本查看对应的压缩格式,然后解压即可

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