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

sxazf · 发布于 2017年03月13日 · 最后由 unclex 回复于 2017年03月16日 · 最后更新自管理员 Lihuazhang · 1745 次阅读

前言

现在大家都认可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

共收到 52 条回复
4821

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

352286
sxazf · #2 · 2017年03月13日 作者

😀

352286
sxazf · #3 · 2017年03月13日 作者
4821phicomm123 回复

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

14879

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

352286
sxazf · #5 · 2017年03月13日 作者
14879dadeshuo 回复

jmeter里加入zson.jar即可。😀

1756

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

352286
sxazf · #7 · 2017年03月13日 作者
1756lucasluo 回复

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

340457

类似的做了一个,区别点

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

Json增删改查👍一个

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

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

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

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

340457
352286sxazf 回复

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

50

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

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

zson支持!😃

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

多谢捧场!

9c2692

个人觉着,这个的特点在于单测时可以对数据的MOCK有一定帮助
但对于json解析这块没啥特别之处。

692

飞哥来了,必须顶!d=====( ̄▽ ̄*)b

15783

分享下自己项目中对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
    }
}
352286
sxazf · #19 · 2017年03月13日 作者
9c2692xushizhao 回复

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

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

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

9c2692
352286sxazf 回复

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

352286
sxazf · #22 · 2017年03月13日 作者
9c2692xushizhao 回复

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

23楼 已删除
9c2692

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

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

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

352286
sxazf · #25 · 2017年03月13日 作者
9c2692xushizhao 回复

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

3341

飞总好

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

😀 多谢支持

15715

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

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

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

15715
352286sxazf 回复

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

110
352286sxazf 回复

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

32楼 已删除
9c2692
352286sxazf 回复

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

1011
352286sxazf 回复

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

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

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

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

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

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

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

15715
352286sxazf 回复

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

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

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

15715
352286sxazf 回复

我也是测试,共勉。

7504
352286sxazf 回复

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

1756
352286sxazf 回复

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

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

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

1011
352286sxazf 回复

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

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

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

1756
352286sxazf 回复

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

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

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

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

692

唉,飞哥是我们的前辈!

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

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

6233

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

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

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

10928

飞总666

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