现在大家都认可 Python 在接口测试方面效率比较高,究其原因,可能是 Python 的请求库功能强大,但 Java 的 HttpClient 封装得好的话,也可以一句代码发送请求,还有一点,Java 的 TestNg 我个人认为是一个非常强大的测试框架,Python 中的那些测试框架应该没有与之比肩的,但即便始此,Java 在接口测试上还是举步维艰,这是因为在请求后对结果的处理,Python 天然支持 json 解析,而 Java 呢?得依靠第三方库,且解析取值得一大片代码,更见鬼的是这一大片代码是毫无复用性可言,更有甚者,在解析时会搞一个 pojo 文件,更让 Python 者觉得用 Java 简直是灾难。
为了解决测试人员在 Java 对 json 解析的困惑,zson 就应运而生了。因为我本人做过 UI 自动化测试,对 XPATH 有一定的了解,所以 zson 对 json 的操作中加入了一个类似于 xpath 的路径的概念,即利用一个路径来操作 json 串。如果一个 json 串有非常复杂的层级关系,如果想获取最里面的某个 key 的值,正常情况下那就得一层一层的解析进去,非常的繁琐,如果用 zson,只需要一句代码,给定一个路径(值得注意的是,也可以是相对路径哦),就可以获取到对应的值,这样可以大大的提高生产力。
#### 专为测试人员打造的 JSON 解析器
当然,有很多很好的 JSON 解析的 JAR 包,比如 fastjson,GSON,甚至也有为我们测试人员而打造的 JSONPATH,但我还是自已实现了一下,也不是没事造轮子,因为我是站在测试人员的立场来设计及实现这个工具的。其主要特点是用一个类似于 xpath 的选择器来获取相应的值。
#### 特点
设定一个 json 串:
{
"retCode": "200",
"retMsg": "success",
"data": [
{
"id": 1,
"name": "test",
"date": "2017-01-09 13:30:00"
},
{
"id": 2,
"name": "test1",
"date": "2017-01-09 13:40:00"
}
]
}
如果想要获取以上 json 串的所有"name"的值,对于正常解析,你得遍历,但对于 zson,你只需要这样:
ZsonResult zr = ZSON.parseJson(json);
List<Object> names = zr.getValues("//name");
我们在进行结果断言时,有时候请求返回的一整个 json 串作为一个期望值来进行断言,但 json 串中往往会存在有不固定的值,比如上面 json 串的"date",每次都是变化的,这样就不好断言了,于是,在 zson 中,我们可以把这个 date 的值进行更改,改成一个固定的值:
ZsonResult zr = ZSON.parseJson(json);
zr.updateValue("//date","0000-00-00 00:00:00");
或者干脆删除这个结点:
ZsonResult zr = ZSON.parseJson(json);
zr.deleteValue("//date");
以上 zson 对 json 串的操作包含了查找,更新,删除。zson 还有对 json 串中增加一个子字符串的操作:
ZsonResult zr = ZSON.parseJson(json);
zr.addValue("/data",2,"{\"id\":3,\"name\":\"test2\",\"date\":\"2017-01-09 14:30:00\"}");
示例一:
[
{
"firstName": "Eric",
"lastName": "Clapton",
"instrument": "guitar"
},
{
"firstName": "Sergei",
"lastName": "Rachmaninoff",
"instrument": "piano"
}
]
找出第二个 firstName: /*[1]/firstName
输出:Sergei
找出第一个 Map: /*[0]
输出:{"firstName": "Eric","lastName": "Clapton","instrument": "guitar"}
找出所有的 firstName: //firstName
输出:["Eric","Sergei"]
示例二:
{"a":["a"],"cb":{"a":1},"d":["a",{"a":[1,2]},{"a":2},""],"e":"b"}
路径: /d/*[1]/a
输出:[1,2]
路径: /d/*[1]/a/*[0]
输出:1
赞一个。 不过,个人觉得单元框架都差不多吧 就像 python 的 nose 用起来肯定比 unittest 爽 但你敢说 nose 就比 unittest 高明了?
nose 与 unittest 都是 python 系的吧?我完全不懂 python 啊。。但我个人还是认为 testng 是比较优秀的测试框架了!
感谢分享。。。遇上 jmeter 不得不用 java。。。习惯敲 python 的欲哭无泪
没看出来有什么特别的,超越 rest-assured + json path 就放弃吧。
类似的做了一个,区别点
Json 增删改查👍一个
其实在解析时,把路径都生成了,且用路径做了一个索引,这样查找起来相当的快。但我没有提供出获取所有路径的 API,其实这里的路径就相当于 json schema 了。
因为返回的 json 串中可能有动态的,比如里面有个时间戳,这样不方便我们做比较,所以把这个时间戳进行替换或者删除就是一个比较好的解决方案了,zson 支持!
替换吧,不建议做删除,替换可以确保有这个 key,但是替换还要准备一个初始数据,所以我是检查到这个 Key 值时直接跳过,写 case 的时候方便一些吧
作者写这个确实是方便测试的角度出发
个人觉着,这个的特点在于单测时可以对数据的 MOCK 有一定帮助
但对于 json 解析这块没啥特别之处。
飞哥来了,必须顶!d=====( ̄▽ ̄*) b
分享下自己项目中对 json 处理的一些想法。
类 xpath 获取对应的值是很方便的,平时写 Selenium 的 case 大多用 xpath,在做 REST API 测试时,处理 json 一般是有两种办法:
按照 path 取值
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(s);
int id = root.at("/data/0/id").asInt();
String name = root.at("/data/0/name").asText();
定义 Python/Java 对象 mapping 到 json
在实际使用中,如果 json 结构和深度非常复杂,第一种办法在组装 request body 和解析 response body 时会很繁琐,并且代码不再那么整洁,所以后来大多用第二种方法。
{
"type": "ServerProfileV6",
"name": "Profile101",
"serverHardwareUri": "/rest/server-hardware/{serverUUID}",
"affinity": "Bay",
"macType": "Virtual",
"serialNumberType": "Virtual",
"wwnType": "Virtual",
"hideUnusedFlexNics":true,
"connections": [{
"id": 1,
"name":"connection1",
"functionType": "Ethernet",
"portId": "Flb 1:1-a",
"requestedMbps": 2500,
"networkUri": "/rest/ethernet-networks/{networkUUID}",
"boot": {
"priority": "Primary"
}
},
{
"id": 2,
"functionType": "Ethernet",
"portId": "Auto",
"requestedMbps": 2500,
"networkUri": "/rest/network-sets/{networkSetUUID}",
"boot": {
"priority": "Secondary"
}
},
{
"id": 3,
"functionType": "FibreChannel",
"portId": "Auto",
"requestedMbps": 2500,
"networkUri": "/rest/fc-networks/{fcNetworkID}",
"boot": {
"priority": "Primary",
"bootVolumeSource": "UserDefined",
"targets": [{
"arrayWwpn": "{arrayWwpn}",
"lun": "{lun}"
}]
}
},
{
"id": 4,
"functionType": "Ethernet",
"portId": "Auto",
"requestedMbps": 2500,
"macType": "UserDefined",
"mac": "12:11:11:11:00:00",
"networkUri": "/rest/network-sets/{networkSetUUID}",
"boot": {
"priority": "NotBootable"
}
},
{
"id": 5,
"functionType": "FibreChannel",
"portId": "Auto",
"requestedMbps": 2500,
"wwpnType":"UserDefined",
"wwnn":"10:00:1C:11:00:00:00:00",
"wwpn":"10:00:1C:11:00:00:00:01",
"macType":"UserDefined",
"mac":"12:11:11:00:00:00",
"networkUri": "/rest/fc-networks/{fcNetworkID}",
"boot": {
"priority": "Secondary",
"bootVolumeSource": "UserDefined",
"targets": [{
"arrayWwpn": "{arrayWwpn}",
"lun": "{lun}"
}]
}
}],
"boot": {
"manageBoot": true,
"order": ["PXE",
"HardDisk",
"CD",
"Floppy",
"USB"]
},
"bios": {
"manageBios": true,
"overriddenSettings": [{
"id": "91",
"value": "1"
},
{
"id": "158",
"value": "2"
}]
},
"localStorage": {
"sasLogicalJBODs": [
{
"id": 1,
"deviceSlot": "Mezz 1",
"name": "Data Storage",
"numPhysicalDrives": 1,
"driveMinSizeGB": 200,
"driveMaxSizeGB": 600,
"driveTechnology": "SasHdd",
"sasLogicalJBODUri": null
},
{
"id": 2,
"deviceSlot": "Mezz 1",
"name": "Recovery Volume",
"numPhysicalDrives": 2,
"driveMinSizeGB": 200,
"driveMaxSizeGB": 600,
"driveTechnology": "SasHdd",
"sasLogicalJBODUri": null
}],
"controllers": [
{
"deviceSlot": "Embedded",
"mode": "RAID",
"initialize": false,
"importConfiguration": false,
"logicalDrives": [
{
"name": "Operating System",
"raidLevel": "RAID1",
"bootable": true,
"numPhysicalDrives": 2,
"driveTechnology": null,
"sasLogicalJBODId": null
}]
},
{
"deviceSlot": "Mezz 1",
"mode": "RAID",
"initialize": false,
"importConfiguration": false,
"logicalDrives": [
{
"name": null,
"raidLevel": "RAID0",
"bootable": false,
"numPhysicalDrives": null,
"driveTechnology": null,
"sasLogicalJBODId": 1
},
{
"name": null,
"raidLevel": "RAID1",
"bootable": false,
"numPhysicalDrives": null,
"driveTechnology": null,
"sasLogicalJBODId": 2
}]
}]
},
"firmware": {
"manageFirmware": true,
"firmwareBaselineUri": "/rest/firmware-drivers/{fwBaselineId}",
"forceInstallFirmware": false
}
}
jsonpath 是很强大,zson 是一个追赶者,所以还需要各位多多的支持一下,就算为了情怀嘛:测试人员自已写的测试工具!
说实话我不太喜欢情怀这个词,也没必要什么都上升到情怀和测试人员自己写的测试工具上(真正的情怀到底是什么我觉着也是大家都在想的事情)
我也只是实话实说,哪些现成工具已经实现了这些功能罢了
在我个人来看 JSON 解析对于其他工具来说没有特别之处,不过我认可修改数据格式这个功能,但具体实用场景还有待考究
很中肯的意见。正如所说,为了实现对 json 串的 crud 操作,然后就不得不去实现了一个合理的数据结构,于是为了满足这个数据结构而去进行了 JSON 的解析,其实最开始这个需求是来源于接口测试平台的编写,在一个平台中,为了获取值,做一个路径选择器是比较好的方式。一环扣一环,最后,出现了 zson,也算是我为测试界做点微薄的贡献吧!
飞总好
我非常赞同作者的精神,认可作者的态度。只是稍微提个小小小的意见,看了源码后发现代码中有大段的 if-else 嵌套,如果可以重构一下就更好了。
非学点好的意见,我曾经想过去修改,但当我发现如果用一些所谓的设计模式,务必会损耗系统资源,且当我去看了 fastjson 的源码后,更加坚定这里用 if-else 是非常好的选择,因为 fastjson 也是这样干的!但是我这里面的 if-else 的结构是可以进行修改且优化的,以后肯定会优化的。
首先,重构不代表就是非要使用什么设计模式不可,这完全是两个不同的概念。可以将部分功能提取出来作为一个方法,以一种更面向对象的方式展示。在主方法逻辑中可以通过调用各个方法的方式,使得代码更清晰易读,简洁明了。然后,我不是很喜欢用 “所谓的设计模式” 这个词,表明作者对设计模式不是很尊重,或者没有很好体会到它的美妙之处。我不是推崇非要使用设计模式的代码才是好的代码,但也不认可有一些人觉得别人老提设计模式就是装 x。其实设计模式更多的是一种编程的思想,是将众多优秀项目中的实践思想提取出来的结果,是前人经验的总结,确实在代码层次设计以及复用性、迁移性、维护性方面有很显著的效果,使得代码展示的更优雅。面向对象的代码更应该像说故事一样,一个方法就是一个动作,如果非常复杂,涉及到众多的分支,可以将其拆分成众多小的动作,最后以组合拳的形式展示。
谈不上指教,相互学习咯,昨天言语有点激烈,如有冒犯还请见谅。我也是初学者,因为不是 cs 专业出身,基础比较薄弱,但也比较重视打基础的工作。昨天看了你的博客,写了不少了。感觉你的 java 基础比我要好,反射、注解使用的都比较娴熟。但是提个个人的意见,我觉得很多方面你都涉猎了,但是都没有再深入去挖掘一下。
{"a":["a"],"cb":{"a":1},"d":["a",{"a":[1,2]},{"a":2},""],"e":"b"}
路径: /d/[1]/a/[0]
输出:1
正确的写法应该是/d/*[1]/a/*[0] ,数组前面加 * 号,我也是看 github 才知道的,请自己发之前先校验下吧。
看了一堆评论,分享精神是需要支持的,点赞,我觉得测试就是要这种精神,创新意识很重要,不需要因为他人意见去完全改变,只吸收自己觉得好的👌
—— 来自 TesterHome 官方 安卓客户端
唉,飞哥是我们的前辈!
多谢支持!我写了众多的口水博客,zson 应该是我最得意之作了,抛开代码层面,解析后的数据结构才是 zson 的精髓,我花了大量的时间去思考并验证,这才是收获最大的地方!
json 不是一般直接一句代码直接编译成对象的咩,为什么还需要 xpath 检查 233
飞总 666
支持相对路径能不能举个例子,怎么用
比如:
[
{
"firstName": "Eric",
"lastName": "Clapton",
"instrument": "guitar"
},
{
"firstName": "Sergei",
"lastName": "Rachmaninoff",
"instrument": "piano"
}
]
想取所有的 firstName,就用 zr.getValues("//firstName");
很有用,顶一个!