乐趣区

关于软件测试:利器-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"));
}

将上述各个断言语法写在一起,理论运行校验后果:

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

  • 返回字段较多,无奈保障每个字段都写断言
  • 避免客户端未做 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…
XmlPath:
https://www.javadoc.io/doc/io…
JsonSchema:
https://json-schema.org/under…

退出移动版