开源测试工具 专为测试人员打造的 json 解析 jar 包--zson

sxazf · 2017年03月13日 · 最后由 sxazf 回复于 2018年03月21日 · 最后更新自管理员 Lihuazhang · 1094 次阅读

前言

现在大家都认可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,只需要一句代码,给定一个路径(值得注意的是,也可以是相对路径哦),就可以获取到对应的值,这样可以大大的提高生产力。

zson

####专为测试人员打造的JSON解析器

当然,有很多很好的JSON解析的JAR包,比如fastjson,GSON,甚至也有为我们测试人员而打造的JSONPATH,但我还是自已实现了一下,也不是没事造轮子,因为我是站在测试人员的立场来设计及实现这个工具的。其主要特点是用一个类似于xpath的选择器来获取相应的值。


####特点

  • 无需层层解析
  • 根据给定的路径(类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\"}");

选择器path说明

示例一:

[
{
"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


源码下载地址

https://github.com/zhangfei19841004/zson

共收到 54 条回复 时间 点赞

赞一个。 不过,个人觉得单元框架都差不多吧 就像python的nose用起来肯定比unittest爽 但你敢说nose就比unittest高明了?

phicomm123 回复

nose与unittest都是python系的吧?我完全不懂python啊。。但我个人还是认为testng是比较优秀的测试框架了!

😂 感谢分享。。。遇上jmeter不得不用java。。。习惯敲python的欲哭无泪

dadeshuo 回复

jmeter里加入zson.jar即可。😀

没看出来有什么特别的,超越rest-assured + json path就放弃吧。

lucasluo 回复

支持相对路径,支持对json串的增删改查,这应该算是一个特点吧。😃

类似的做了一个,区别点

  1. 自定义的规则用了JsonView展示规则
  2. 不要的字段同样定义一个参数存储,校验时不校验这些字段的Value值

Json增删改查👍一个

sxazf #10 · 2017年03月13日 作者
Roc 回复

其实在解析时,把路径都生成了,且用路径做了一个索引,这样查找起来相当的快。但我没有提供出获取所有路径的API,其实这里的路径就相当于json schema了。

sxazf #11 · 2017年03月13日 作者
Roc 回复

因为返回的json串中可能有动态的,比如里面有个时间戳,这样不方便我们做比较,所以把这个时间戳进行替换或者删除就是一个比较好的解决方案了,zson支持!

sxazf 回复

替换吧,不建议做删除,替换可以确保有这个key,但是替换还要准备一个初始数据,所以我是检查到这个Key值时直接跳过,写case的时候方便一些吧

作者写这个确实是方便测试的角度出发👍

sxazf #14 · 2017年03月13日 作者
Roc 回复

zson支持!😃

sxazf #15 · 2017年03月13日 作者
simple 回复

多谢捧场!

个人觉着,这个的特点在于单测时可以对数据的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时会很繁琐,并且代码不再那么整洁,所以后来大多用第二种方法。

比如
http://h17007.www1.hpe.com/docs/enterprise/servers/oneview3.0/cic-api/en/api-docs/current/index.html#rest/server-profiles

{
"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
}
}
sxazf #19 · 2017年03月13日 作者
xushizhao 回复

主要是在解析时,根据路径就可得出值,无须层层解析进去。

sxazf #20 · 2017年03月13日 作者
ovpt 回复

可以用相对路径!这也是zson的一大特点!

sxazf 回复

我记得rest 重新封装的那个jsonpath 就是这个功能呀。。。

sxazf #22 · 2017年03月13日 作者
xushizhao 回复

jsonpath是很强大,zson是一个追赶者,所以还需要各位多多的支持一下,就算为了情怀嘛:测试人员自已写的测试工具!😃

23楼 已删除

说实话我不太喜欢情怀这个词,也没必要什么都上升到情怀和测试人员自己写的测试工具上(真正的情怀到底是什么我觉着也是大家都在想的事情)

我也只是实话实说,哪些现成工具已经实现了这些功能罢了

在我个人来看JSON解析对于其他工具来说没有特别之处,不过我认可修改数据格式这个功能,但具体实用场景还有待考究

sxazf #25 · 2017年03月13日 作者
xushizhao 回复

🙏 很中肯的意见。正如所说,为了实现对json串的crud操作,然后就不得不去实现了一个合理的数据结构,于是为了满足这个数据结构而去进行了JSON的解析,其实最开始这个需求是来源于接口测试平台的编写,在一个平台中,为了获取值,做一个路径选择器是比较好的方式。一环扣一环,最后,出现了zson,也算是我为测试界做点微薄的贡献吧!😀

sxazf #27 · 2017年03月13日 作者
jaychang1989 回复

😀 多谢支持

我非常赞同作者的精神,认可作者的态度。只是稍微提个小小小的意见,看了源码后发现代码中有大段的if-else嵌套,如果可以重构一下就更好了。

sxazf #29 · 2017年03月13日 作者
zni.feng 回复

非学点好的意见,我曾经想过去修改,但当我发现如果用一些所谓的设计模式,务必会损耗系统资源,且当我去看了fastjson的源码后,更加坚定这里用if-else是非常好的选择,因为fastjson也是这样干的!但是我这里面的if-else的结构是可以进行修改且优化的,以后肯定会优化的。😀

sxazf 回复

首先,重构不代表就是非要使用什么设计模式不可,这完全是两个不同的概念。可以将部分功能提取出来作为一个方法,以一种更面向对象的方式展示。在主方法逻辑中可以通过调用各个方法的方式,使得代码更清晰易读,简洁明了。然后,我不是很喜欢用“所谓的设计模式”这个词,表明作者对设计模式不是很尊重,或者没有很好体会到它的美妙之处。我不是推崇非要使用设计模式的代码才是好的代码,但也不认可有一些人觉得别人老提设计模式就是装x。其实设计模式更多的是一种编程的思想,是将众多优秀项目中的实践思想提取出来的结果,是前人经验的总结,确实在代码层次设计以及复用性、迁移性、维护性方面有很显著的效果,使得代码展示的更优雅。面向对象的代码更应该像说故事一样,一个方法就是一个动作,如果非常复杂,涉及到众多的分支,可以将其拆分成众多小的动作,最后以组合拳的形式展示。

sxazf 回复

说不定哪天 fastjson 重构了用了设计模式,然后注释这段以前太chaos了。

32楼 已删除
sxazf 回复

欢迎纯技术交流,不欢迎广告推广🕵

sxazf 回复

你还能再low点么,设计模式浪费系统资源?你java是体育老师教的么,丢java的脸

sxazf #35 · 2017年03月14日 作者
DoctorQ 回复

不好意思,我刚刚开始学习JAVA,以后我会尽量说我不会写JAVA,免得丢了你们JAVA大佬的脸!😃

sxazf #37 · 2017年03月14日 作者
zni.feng 回复

下一步我会重构一下,重点去学习一下设计模式,到时候还望多多指教!🙏

sxazf #38 · 2017年03月14日 作者
Lihuazhang 回复

easy,对于有硬实力的人,我是很尊敬的!🙏

sxazf 回复

谈不上指教,相互学习咯,昨天言语有点激烈,如有冒犯还请见谅。我也是初学者,因为不是cs专业出身,基础比较薄弱,但也比较重视打基础的工作。昨天看了你的博客,写了不少了。感觉你的java基础比我要好,反射、注解使用的都比较娴熟。但是提个个人的意见,我觉得很多方面你都涉猎了,但是都没有再深入去挖掘一下。

sxazf #40 · 2017年03月14日 作者
zni.feng 回复

我作为一个测试人员,在代码方面确实有很多欠缺,但我在努力弥补,希望有朝一日能跟上你们的脚步!🙏

sxazf 回复

我也是测试,共勉。

sxazf 回复

{"a":["a"],"cb":{"a":1},"d":["a",{"a":[1,2]},{"a":2},""],"e":"b"}
路径: /d/[1]/a/[0]
输出:1
正确的写法应该是/d/*[1]/a/*[0] ,数组前面加*号,我也是看github才知道的,请自己发之前先校验下吧。

sxazf 回复

不用谦虚,跟他们互怼。技术上怕啥啊

sxazf #44 · 2017年03月14日 作者
konami1986 回复

是的,这个是我的错,我是直接从GITHUB上拷贝过来的,我再来检查一下。

sxazf 回复

这个态度是技术人应有的态度,带着这个态度,你就会进步。当然我言语也有激烈之处,还请海涵。

sxazf #46 · 2017年03月14日 作者
lucasluo 回复

不是怕,硬实力不够,我在技术上确实有所欠缺!

sxazf 回复

你的技术比他们高出一个珠穆朗玛峰。加油。

sxazf 关闭了讨论 03月14日 12:43
Lihuazhang 关闭了讨论 03月14日 15:20
sxazf 关闭了讨论 03月14日 15:33
sxazf 重新开启了讨论 03月14日 21:29

看了一堆评论,分享精神是需要支持的,点赞,我觉得测试就是要这种精神,创新意识很重要,不需要因为他人意见去完全改变,只吸收自己觉得好的👌

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

唉,飞哥是我们的前辈!

sxazf #55 · 2017年03月15日 作者
hu_qingen 回复

多谢支持!我写了众多的口水博客,zson应该是我最得意之作了,抛开代码层面,解析后的数据结构才是zson的精髓,我花了大量的时间去思考并验证,这才是收获最大的地方!🙏

json不是一般直接一句代码直接编译成对象的咩,为什么还需要xpath检查 233

sxazf #57 · 2017年03月16日 作者
dongdong 回复

解析成对象后 才是根据路径选择器来操作

飞总666

支持相对路径能不能举个例子,怎么用

sxazf #60 · 2018年03月21日 作者
hengfei 回复

比如:
[
{
"firstName": "Eric",
"lastName": "Clapton",
"instrument": "guitar"
},
{
"firstName": "Sergei",
"lastName": "Rachmaninoff",
"instrument": "piano"
}
]
想取所有的firstName,就用zr.getValues("//firstName");

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