利器 | REST Assured 实践(二):断言实现

简介: 利器 | REST Assured 实践(二):断言实现

在上一篇文章中,我们初步探讨了 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 进行断言:

@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写法

@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],写法如下:

@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

@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 官方给的一个实例做演示

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

再次在本地搭起一个临时服务:

若我们要对第二个 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"));
}

将上述各个断言语法写在一起,实际运行校验结果:

1080×757 61.6 KB

在实际工作中,对接口返回值进行断言校验,除了常用字段的断言检测以外,还要对其他字段的类型进行检测,原因在于:

  • 返回字段较多,无法保证每个字段都写断言
  • 防止客户端未做 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完成支持

<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"));
}

运行结果:

  • 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 的获取与处理了,后续文章继续探讨。

更多技术文章

相关文章
|
2月前
|
JSON Go API
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
|
2月前
|
JSON API 数据格式
深度剖析!Python Web 开发中 RESTful API 的每一个细节,你不可不知的秘密!
在 Python Web 开发中,RESTful API 是构建强大应用的关键,基于 Representational State Transfer 架构风格,利用 HTTP 卞性能。通过 GET、POST、PUT 和 DELETE 方法分别实现资源的读取、创建、更新和删除操作。示例代码展示了如何使用 Flask 路由处理这些请求,并强调了状态码的正确使用,如 200 表示成功,404 表示未找到资源等。
48 5
|
2月前
|
存储 JavaScript NoSQL
深入浅出后端开发:构建你的第一个RESTful API
【9月更文挑战第17天】在数字时代的浪潮中,后端开发是支撑起整个互联网的骨架。本文将引导读者了解后端开发的基本概念,并通过一个实际的代码示例,展示如何从零开始构建一个简单的RESTful API。我们将一起探索API设计的哲学、选择合适的后端语言和框架,以及实现数据存储和接口测试的过程。无论你是编程新手,还是希望扩展你的技术栈,这篇文章都将为你提供一次全面而深入的后端开发之旅。
41 0
|
4月前
|
JSON API 网络架构
gRPC 与 REST 的比较分析:哪种 API 适合您的开发需求?
gRPC, 由 Google 推出的开源远程过程调用(RPC)框架, 使两个应用程序间的方法调用变得简单,支持结构化数据的交换。通过采用 Protocol Buffers (Protobuf) ——一种与语言无关的接口定义语言,gRPC 体现了许多现代网络通信技术的优势
gRPC 与 REST 的比较分析:哪种 API 适合您的开发需求?
|
3月前
|
API Java Python
API的神秘面纱:从零开始构建你的RESTful服务
【8月更文挑战第31天】在现代网络应用开发中,RESTful API已成为数据交互的标准。本文通过比较流行的技术栈(如Node.js、Python的Django和Flask、Java的Spring Boot)及其框架,帮助你理解构建RESTful API的关键差异,涵盖性能、可扩展性、开发效率、社区支持、安全性和维护性等方面,并提供示例代码和最佳实践,指导你选择最适合项目需求的工具,构建高效、安全且易维护的API服务。
53 0
|
5月前
|
JSON 安全 API
技术经验解读:使用Refit框架访问REST接口
技术经验解读:使用Refit框架访问REST接口
81 0
|
6月前
|
JSON 缓存 API
title: 深入理解REST API设计的最佳实践
title: 深入理解REST API设计的最佳实践
65 0
|
JSON Java API
使用 REST-assured 获取并断言响应数据
使用 REST-assured 获取并断言响应数据
|
XML JSON API
寻求 REST
寻求 REST
53 0
|
JSON 关系型数据库 MySQL
接口自动化 基于python实现的http+json协议接口自动化测试框架源码(实用改进版)
接口自动化 基于python实现的http+json协议接口自动化测试框架源码(实用改进版)
148 0