1、Json断言
1.1 环境准备
这里以rest-assured官方给的一个示例做演示学习
{
"lotto":{
"lottoId":5,
"winning-numbers":[2,45,34,23,7,5,3],
"winners":[{
"winnerId":23,
"numbers":[2,45,34,23,3,5]
},{
"winnerId":54,
"numbers":[52,3,12,11,18,22]
}]
}
}
在本地使用python -m CGIHTTPServer
临时搭建起一个服务:
1.2 JsonPath(Groovy’s GPath)
在Groovy的官网,虽然并未提及它在
json
中的使用,但实际上只要是树形的层级关系,无论是json
、xml
或者其他格式,就可以使用这种简单的语法帮我们去找到其中的值,rest-assured
也已经帮我们实现支持了GPath
的断言方式
- 根节点.子节点
1)我们可以使用根节点.(点)子节点的方式一层层的找下去,例如我们需要对lottoId
等于5进行断言:
2)如果我们想要断言@Test void testGPath(){ given(). when(). log().all().get("http://127.0.0.1:8000/restAssured.json"). then(). log().all().body("lotto.lottoId",equalTo(5)); }
winners
数组下面的winnerId
,检查23和54是否包含其中,可以如下lotto.winners.winnerId
写法
@Test void testGPath(){ given(). when(). log().all().get("http://127.0.0.1:8000/restAssured.json"). then(). log().all() .body("lotto.winners.winnerId",hasItems(54,23)); }
- 索引取值
1)如果我们想要取某些相同字段中的某一个,可以使用类似索引的方式获取,例如想要断言winners
数组下面的winnerId
的第一个值是否为23,可以使用lotto.winners.winnerId[0]
,写法如下:
2)如果我们想要取某些相同字段中的最后一个,可以使用@Test void testGPath(){ given(). when(). log().all().get("http://127.0.0.1:8000/restAssured.json"). then(). log().all() .body("lotto.winners.winnerId[0]",equalTo(23)); }
-1
作为索引,例如断言断言winners
数组下面的winnerId
的最后一个的值是否为54
@Test void testGPath(){ given(). when(). log().all().get("http://127.0.0.1:8000/restAssured.json"). then(). log().all() .body("lotto.winners.winnerId[-1]",equalTo(54)); }
- findAll
有时候我们需要获取符合某些条件的结果来进行断言,这里findAll
可以帮助我们实现,我们可以在findAll
方法中写筛选条件,例如我们想取winnerId
的值在大于或等于30小于60之间的结果进行断言,具体写法如下:
@Test void testGPath(){ given(). when(). log().all().get("http://127.0.0.1:8000/restAssured.json"). then(). log().all() .body("lotto.winners.findAll{ winners -> winners.winnerId >= 30 && winners.winnerId < 60}.winnerId[0]",equalTo(54)); }
- find
find的用法与findAll基本一致,只是find默认取匹配到的第一个:
@Test void testGPath(){ given(). when(). log().all().get("http://127.0.0.1:8000/restAssured.json"). then(). log().all() .body("lotto.winners.find{ winners -> winners.winnerId >= 30 && winners.winnerId < 60}.winnerId",equalTo(54)); }
- 索引取值
1.3 实操演示
将上述各个断言语法写在一起,实际运行校验结果:
2、XML断言
2.1 环境准备
上面介绍了,GPath
也支持XML
格式的断言,这里再以rest-assured官方给的一个实例做演示
<shopping>
<category type="groceries">
<item>
<name>Chocolate</name>
<price>10</price>
</item>
<item>
<name>Coffee</name>
<price>20</price>
</item>
</category>
<category type="supplies">
<item>
<name>Paper</name>
<price>5</price>
</item>
<item quantity="4">
<name>Pens</name>
<price>15</price>
</item>
</category>
<category type="present">
<item when="Aug 10">
<name>Kathryn's Birthday</name>
<price>200</price>
</item>
</category>
</shopping>
再次在本地搭起一个临时服务:
2.2 XmlPath断言语法
- 若我们要对第二个name的值Coffee进行断言,写法如下:
@Test void testXML(){ when(). get("http://127.0.0.1:8000/restAssured.xml"). then(). log().all(). body("shopping.category[0].item[1].name",equalTo("Coffee")); }
- size()
可以利用size()
方法来获取对应节点的数量,例如这里要断言category
的数量:@Test void testXML(){ when(). get("http://127.0.0.1:8000/restAssured.xml"). then(). log().all() .body("shopping.category.size()",equalTo(3)); }
- it.@type、it.price
在xml中断言中,可以利用it.属性或节点的值来作为筛选条件;
例如这里要获取type
为supplies
的category
下的第一个item
的name
;
以及获取price
为10的商品名name
。
@Test void testXML(){ when(). get("http://127.0.0.1:8000/restAssured.xml"). then(). log().all() .body("shopping.category.findAll{ it.@type == 'supplies' }.item[0].name",equalTo("Paper")) .body("shopping.category.item.findAll{ it.price == 10 }.name",equalTo("Chocolate")); }
- **.findAll
对于xml中有一个特别的语法,**.findAll
,可以直接忽略前面的节点,直接对筛选条件进行匹配,依然获取price
为10的商品名name
,写法如下:@Test void testXML(){ when(). get("http://127.0.0.1:8000/restAssured.xml"). then(). log().all() .body("**.findAll{ it.price == 10 }.name",equalTo("Chocolate")); }
2.3 实操演示
将上述各个断言语法写在一起,实际运行校验结果:
3、JsonSchema断言
3.1 需求背景
在实际工作中,对接口返回值进行断言校验,除了常用字段的断言检测以外,还要对其他字段的类型进行检测,原因在于:
- 1、返回字段较多,无法保证每个字段都写断言
- 2、防止客户端未做null值的校验判断,如果因为版本变更或网络等原因造成某个不能接收null值的返回字段为null,就很有可能造成软件的崩溃
- 3、某些数值是不能为负的
- 4、小数点保留位数,对于股票的交易、医疗数据的分析,小数点的精确度都是有其实际价值的
对返回的字段一个个写断言显然是非常耗时的,这个时候就需要一个模板,可以定义好数据类型和匹配条件,除了关键参数外,其余可直接通过此模板来断言,这个就要请出JsonSchema
了
3.2 使用方法
先对上述的json例子做少许修改,增加一个String类型的winnername
字段,这里可以先你不用疑惑为什么加,后续自有其演示作用
3.2.1 JsonSchema模板生成
1)首先要借助于Json schema tool
的网站https://www.jsonschema.net/,将返回json字符串复制到页面左边,然后点击INFER SHCEMA
,就会自动转换为schema json
文件类型,会将每个地段的返回值类型都设置一个默认类型;
在pattern
中也可以写正则进行匹配
2)点击“设置”
按钮会出现各个类型返回值更详细的断言设置,这个就是schema
最常用也是最实用的功能,也可以对每种类型的字段最更细化的区间值校验或者断言,例如长度,取值范围等,具体感兴趣的话可以从官网学习深入学习;平常对重要字段的校验我通常会选用其他断言,比如hamcrest
断言
3)选择复制功能,可以将生成的schema
模板保存下来
3.2.2 rest-assured结合使用
4)添加maven依赖,在rest-assured
完成支持
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>4.0.0</version>
</dependency>
5)使用matchesJsonSchemaInClasspath
方法对响应结果进行schema
断言
@Test
void jsonSchemaTest(){
get("http://127.0.0.1:8000/restAssured.json").
then().log().all()
.body(matchesJsonSchemaInClasspath("jsonSchema.json"));
}
运行结果:
3.2.3 多类型校验-Combining schemas
-
String类型的默认值为null,后端很有可能在某个字段无值时返回null,例如我们将之前添加的
winnername
字段返回null
:
运行查看断言结果:
很明显用例执行失败,当我们定义了winnername
为String
类型后,返回null
就会断言失败,这显然不符合我们的需求,会造成用例执行结果的误判,这个时候我们需要使winnername
即可以为String
类型,又可以为null
; -
这就要用到
jsonSchema
提供的Combining schemas
方法了
Combining schemas提供了如下几种方式:- allOf
- anyOf
- oneOf
- not
-
这里我们选取
anyOf
(任何一项满足即可)来完成上述的举例,将原来的type
换成String
和null
任何一个都支持的类型:
再次运行用例,查看断言结果:
用例完美通过,到此结束~
4、写在最后
断言的语法不止上述列出的这些,但是日常工作中绝大部分需求都可以满足,如有需要可参考官方文档进去研究:
JsonPath:
https://www.javadoc.io/doc/io.rest-assured/json-path/latest/io/restassured/path/json/JsonPath.html
XmlPath:
https://www.javadoc.io/doc/io.rest-assured/xml-path/latest/io/restassured/path/xml/XmlPath.html
JsonSchema:
https://json-schema.org/understanding-json-schema/
另外,在我们实际工作中,很多时候并不是直接对响应结果直接断言,我们可能需要获取响应结果中的某些值,将这些值传递到下一个接口或者和其他接口的响应进行比较断言,这就涉及到了对响应response
的获取与处理了,可参考另一篇文章:
03-接口自动化框架REST Assured对Response结果的导出获取