在上一篇文章中,我们初步探讨了 REST Assured 的应用实践,还有很多丰富的用法需要慢慢探索研究。而 REST Assured 提供的完整断言手段,是测试工程师最常用最重要的功能之一。断言该如何使用呢?
这里以 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)我们可以使用根节点.(点)子节点的方式一层层的找下去,例如我们需要对lottoId等于 5 进行断言:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Rxy50cS-1650514201013)(http://www.kaotop.com/file/tupian/20220424/3770a78cf109b1cc8eb443d8a5472b9ba3653b86.png)]
@Test
void testGPath(){
given().
when().
log().all().get("http://127.0.0.1:8000/restAssured.json").
then().
log().all().body("lotto.lottoId",equalTo(5));
}
2)如果我们想要断言winners数组下面的winnerId,检查23和54是否包含其中,可以如下lotto.winners.winnerId写法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WqCqwmaY-1650514205932)(http://www.kaotop.com/file/tupian/20220424/d4e8ad014ed786cb61ea038719908674e4ad04fe.png)]
@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],写法如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5maZ7b3D-1650514210826)(http://www.kaotop.com/file/tupian/20220424/a3aa8f53907041fdbe8035a0005b64b506b7d607.png)]
@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));
}
2)如果我们想要取某些相同字段中的最后一个,可以使用 -1 作为索引,例如断言断言 winners 数组下面的 winnerId 的最后一个的值是否为 54
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xNniuyxj-1650514215993)(http://www.kaotop.com/file/tupian/20220424/0cd8179df05d0e5c04adafb5f2b9e4d750e3788f.png)]
@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));
}
将上述各个断言语法写在一起,实际运行校验结果:
上面介绍了,GPath 也支持 XML 格式的断言,这里再以 rest-assured 官方给的一个实例做演示
-
Chocolate
10
-
Coffee
20
-
Paper
5
-
Pens
15
-
Kathryn's Birthday
200
再次在本地搭起一个临时服务:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wyGnnYQY-1650514239952)(http://www.kaotop.com/file/tupian/20220424/57f3c0efa7c5dc61320183703bec045a05d2d5b6.png)]
若我们要对第二个 name 的值 Coffee 进行断言,写法如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zetUxuXn-1650514241584)(http://www.kaotop.com/file/tupian/20220424/43a7ea3c86847119d32b3d73011f54ae45424cd7.png)]
@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。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tDkm2iMc-1650514251706)(http://www.kaotop.com/file/tupian/20220424/fce1da59aee615a2044d23322c64a25033def93a.png)]
@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"));
}
将上述各个断言语法写在一起,实际运行校验结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C0Z0AuoL-1650514262955)(http://www.kaotop.com/file/tupian/20220424/6581ff21b8eeedebad25359cbffa9748cc30ce61.jpeg)]
在实际工作中,对接口返回值进行断言校验,除了常用字段的断言检测以外,还要对其他字段的类型进行检测,原因在于:
- 返回字段较多,无法保证每个字段都写断言
-
- 防止客户端未做 null 值的校验判断,如果因为版本变更或网络等原因造成某个不能接收 null 值的返回字段为 null,就很有可能造成软件的崩溃
-
- 某些数值是不能为负的
-
- 小数点保留位数,对于股票的交易、医疗数据的分析,小数点的精确度都是有其实际价值的
对返回的字段一个个写断言显然是非常耗时的,这个时候就需要一个模板,可以定义好数据类型和匹配条件,除了关键参数外,其余可直接通过此模板来断言,这个就要请出JsonSchema了
- 小数点保留位数,对于股票的交易、医疗数据的分析,小数点的精确度都是有其实际价值的
先对上述的 json 例子做少许修改,增加一个 String 类型的 winnername 字段,这里可以先你不用疑惑为什么加,后续自有其演示作用
1)首先要借助于Json schema tool的网站https://www.jsonschema.net/,将返回json字符串复制到页面左边,然后点击INFER SHCEMA,就会自动转换为schema json文件类型,会将每个地段的返回值类型都设置一个默认类型; 在pattern中也可以写正则进行匹配
2)点击“设置”按钮会出现各个类型返回值更详细的断言设置,这个就是schema最常用也是最实用的功能,也可以对每种类型的字段最更细化的区间值校验或者断言,例如长度,取值范围等,具体感兴趣的话可以从官网学习深入学习;平常对重要字段的校验我通常会选用其他断言,比如hamcrest断言
3)选择复制功能,可以将生成的schema模板保存下来
4)添加maven依赖,在rest-assured完成支持
io.rest-assured
json-schema-validator
4.0.0
5)使用matchesJsonSchemaInClasspath方法对响应结果进行schema断言
@Test
void jsonSchemaTest(){
get("http://127.0.0.1:8000/restAssured.json").
then().log().all()
.body(matchesJsonSchemaInClasspath("jsonSchema.json"));
}
运行结果:
- String类型的默认值为null,后端很有可能在某个字段无值时返回null,例如我们将之前添加的winnername字段返回null:
运行查看断言结果:
很明显用例执行失败,当我们定义了winnername为String类型后,返回null就会断言失败,这显然不符合我们的需求,会造成用例执行结果的误判,这个时候我们需要使winnername即可以为String类型,又可以为null;
这就要用到jsonSchema提供的Combining schemas方法了 Combining schemas提供了如下几种方式:
- allOf
-
- anyOf
-
- oneOf
-
- not
这里我们选取anyOf(任何一项满足即可)来完成上述的举例,将原来的type换成String和null任何一个都支持的类型:
- not
再次运行用例,查看断言结果:
用例完美通过,到此结束~
断言的语法不止上述列出的这些,但是日常工作中绝大部分需求都可以满足,如有需要可参考官方文档进去研究:
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 的获取与处理了,后续文章继续探讨。
原文链接
获取更多技术文章分享
在 REST Assured 的官方 GitHub 上有这样一句简短的描述: Java DSL for easy testing of REST services 简约的 REST 服务测试 Java DSL
REST Assured 官方的 README 第一句话对进行了一个优点的概述,总的意思表达的就是简单好用。那么 REST Assured 有哪些优点,又该如何使用呢?
用 Java 做接口自动化测试首选 REST Assured,具体原因如下:
- 开源
-
- 简约的接口测试 DSL
-
- 支持 xml json 的结构化解析
-
- 支持 xpath jsonpath gpath 等多种解析方式
-
- 对 spring 的支持比较全面
添加 maven 依赖
- 对 spring 的支持比较全面
io.rest-assured
rest-assured
4.0.0
test
我们对接口进行测试一般由三步曲:传参、发请求、响应结果断言,REST Assured给我们提供了清晰的三步曲,以given、when、then的结构来实现,基本写法如下:
//使用参数
given().
param("key1", "value1").
param("key2", "value2").
when().
post("/somewhere").
then().
body(containsString("OK"))
//使用X-Path (XML only)
given().
params("firstName", "John", "lastName", "Doe").
when().
post("/greetMe").
then().
body(hasXPath("/greeting/firstName[text()='John']"))
请求体body如下
{
"password": "elcrD28ZSLLtR0VLs/jERA\u003d\u003d\n",
"grant_type": "password",
"scope": "server",
"userType": 1,
"username": "xxx"
}
Request Header 如下:
Headers: Authorization=Basic c3lzdGVtxxxRlbQ==
Host=47.103.xxx.133
Accept=*/*
Content-Type=application/json; charset=ISO-8859-1
我们发送请求经常需要带有参数,使用 given() 就可以实现,当时当我们使用 given() 的时候发现其中有很多传参方法如下:
没错,在传参的方法中包含了 param、pathParam、queryParam 和 formParam,下面来研究下这几个传参方法的区别
- param
-
- 通常我们都会使用 given().param 方法来传参,REST Assured 会根据 HTTP 方法自动尝试确定哪种参数类型(即查询或表单参数),如果是 GET,则查询参数将自动使用,如果使用 POST,则将使用表单参数;
- queryParam 和 formParam
-
- 有时候在 PUT 或 POST 请求中,需要区分查询参数和表单参数时,就需要使用queryParam 和 formParam 方法了,具体写法如下:
given().
formParam("formParamName", "value1").
queryParam("queryParamName", "value2").
when().
post("/something")
- pathParam
- 使用given时指定请求路径的参数,这个方法很少用到,或者说我本人几乎没用到过(可能我的修行还不够,踩坑还太少~);具体写法如下:
given().
pathParam("OAuth", "oauth").
pathParam("accessToken", "token").
when().
post("/auth/{OAuth}/{accessToken}").
then().
..
- header/headers
- 经常还需要在请求头中带入参数,这个时候就可以使用header或headers方法,写法如下:
given()
.header("Authorization","Basic c3lzdGVtOxxxbQ==")
.header("Host","47.xxx.xxx.133")
- 或者用headers将多个参数写在一起:
-
-
- given()
-
.headers("Authorization","Basic c3lzdGVtxxx3RlbQ==","Host","47.xxx.xxx.133")
- cookie
- 有时候需要在请求中带入cookie,restassured提供了cookie方法来实现:
- ```
- given()
- .cookie("c_a","aaaaaa")
- .cookie("c_b","bbbbbb"). ..
- contentType
- 经常还会设置contentType,最常见的就是application/json了,写法如下:
given().contentType("application/json"). ..
//或者
given().contentType(ContentType.JSON). ..
- body
- 在POST, PUT 或 DELETE请求中,我们经常还需要带上请求体body,写法如下:
-
- given().body(“{\n” +
-
"\t\"password\": \"elcrD28xxxR0VLs/jERA\u003d\u003d\n\",\n" +
-
"\t\"grant_type\": \"password\",\n" +
-
"\t\"scope\": \"server\",\n" +
-
"\t\"userType\": 1,\n" +
-
"\t\"username\": \"xxx\"\n" +
-
"}")
也可以用request更为明确的指出是请求body:
given().request().body(“{\n” +
“\t"password”: “elcrD28xxxR0VLs/jERA\u003d\u003d\n”,\n" +
“\t"grant_type”: “password”,\n" +
“\t"scope”: “server”,\n" +
“\t"userType”: 1,\n" +
“\t"username”: “xxx”\n" +
“}”)
- 没有参数
- 如果我们没有参数需要传递,也可以省略掉given():
get(“/lotto”).then().assertThat().body(“lotto.lottoId”, equalTo(5));
- proxy
- 有时候我们需要进行接口的调试,抓包是最常用的一种方式,rest-assured 提供了 proxy 方法,可以设置代理,写法如下:
given().proxy(“127.0.0.1”,8888). …
实际运行结果:
- when主要用来触发请求,在when后面接着请求URL:
given().when().post(“http://47.103.xxx.133/auth/oauth/token”). …
- 前面在 given 中我们设置了很多请求参数,在 when 中也可以设置,只不过要注意的是在请求之前设置;这也比较好理解,如果再请求之后的话,参数都设置怎么发请求呢?
given()
.when()
.contentType(ContentType.JSON)
.headers(“Authorization”,“Basic c3lzxxx3RlbQ==”,“Host”,“47.xxx.xxx.133”)
.request().body(“{\n” +
“\t"password”: “elcrD28ZSLLtR0VLs/jERA\u003d\u003d\n”,\n" +
“\t"grant_type”: “password”,\n" +
“\t"scope”: “server”,\n" +
“\t"userType”: 1,\n" +
“\t"username”: “qinzhen”\n" +
“}”)
.post(“http://47.xxx.xxx.133/auth/oauth/token”)
. …
- 断言-then().body()
- then().body() 可以对响应结果进行断言,在 body 中写入断言:
- ```
- .. post("http://47.xxx.xxx.133/auth/oauth/token")
- .then().statusCode(200).body("code",equalTo(1));
其中statusCode(200)是对状态码的断言,判断状态码是否为200; body(“code”,equalTo(1))是对返回体中的 code 进行断言,要求返回 code值为1 。
实 *** 演示:
我们将上述的 given、when、then 结合起来看一下实际运行效果,这里在运行之前再提一个功能,我们可以在 when 和 then 后面加上.log().all(),这样在运行过程中就可以把请求和响应的信息都打印出来:
- 获取响应-then().extract().body().path(“code”)
- 我们可以在 then 后面利用 .extract().body() 来获取我们想要 body 的返回值,它们也可以直接接在断言后面,写法如下:
-
-
- … .then()
-
.log().all().statusCode(200).body("code",equalTo(1))
-
.extract().body().path("code");
实 *** 演示:
演示前再来看一个新的功能,上面我们再写请求体 body 时时这样的:
body(“{\n” +
“\t"password”: “elcrD28ZxxxVLs/jERA\u003d\u003d\n”,\n" +
“\t"grant_type”: “password”,\n" +
“\t"scope”: “server”,\n" +
“\t"userType”: 1,\n" +
“\t"username”: “qinzhen”\n" +
“}”)
看起来有点丑,改造一下;rest-assured 为我们提供了一个利用 HashMap 来创建json 文件的方法,先把要传的字段放入 hashmap 中,然后用 contentType 指明JSON 就可以了,具体写法如下:
HashMap map = new HashMap();
map.put(“password”,“elcrD28ZSLLtR0VLs/jERA\u003d\u003d\n”);
map.put(“grant_type”,“password”);
map.put(“scope”,“server”);
map.put(“userType”,1);
map.put(“username”,“xxx”);
given()
.headers(“Authorization”,“Basic c3lzdGVtxxxlbQ==”,“Host”,“47.xxx.xxx.133”)
.contentType(JSON)
.body(map). …
现在进行完整的请求,获取返回值 code 并打印:
HashMap map = new HashMap();
map.put(“password”,“elcrD28ZSLLtR0VLs/jERA\u003d\u003d\n”);
map.put(“grant_type”,“password”);
map.put(“scope”,“server”);
map.put(“userType”,1);
map.put(“username”,“xxx”);
Integer code =
given()
.headers(“Authorization”,“Basic c3lzdGVtxxxlbQ==”,“Host”,“47.xxx.xxx.133”)
.contentType(JSON)
.body(map).
when()
.log().all().post(“http://47.xxx.xxx.133/auth/oauth/token”).
then()
.log().all().statusCode(200).body(“code”,equalTo(1))
.extract().body().path(“code”);
System.out.println(“返回code的值是:”+code);
运行结果:
![](http://www.kaotop.com/file/tupian/20220424/b21f719a9873c7ba0fbc382eef68edb6.png)
关于REST Assured,这里仅仅算是初步认识。认识它的语法结构和功能,对于更多丰富的用法还需要慢慢探索研究,特别是断言的部分,是测试工程师最常用最终要的功能之一。REST Assured提供的完整断言手段,在后续文章中我们一起探讨。
对于想系统进阶提升测试开发技能的同学,推荐霍格沃兹测试学院出品的 《测试开发从入门到高级实战》系统进阶班课程。
4 个月由浅入深,强化集训,测试大咖通过 8+ 企业级项目实战演练,带你一站式掌握 BAT 一线测试开发工程师必备核心技能(对标阿里巴巴P6+,挑战年薪50W+)!学员直推 BAT 名企测试经理,普遍涨薪 50%+!
![](http://www.kaotop.com/file/tupian/20220424/violation-del.png)
### 测试开发实战干货 | BAT 内推职位 | 大咖公开课
[原文链接](https://mp.weixin.qq.com/s?__biz=MzU3NDM4ODEzMg==&mid=2247486810&idx=1&sn=4aa41547a92e761699b99928bc132442&chksm=fd326f91ca45e68713627d612d29fecabf1e1f4c5657a19f72dce47c16b695c1dfbe16ce2a02#rd)
[获取更多技术文章分享](https://qrcode.ceba.ceshiren.com/link?name=article&project_id=qrcode&from=csdn2×tamp=1650513454)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)