利器 | REST Assured 实践

利器 | REST Assured 实践,第1张


在上一篇文章中,我们初步探讨了 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任何一个都支持的类型:


再次运行用例,查看断言结果:

用例完美通过,到此结束~

断言的语法不止上述列出的这些,但是日常工作中绝大部分需求都可以满足,如有需要可参考官方文档进去研究:

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 依赖

   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)

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/726499.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存