REST-assured Framework 指北

1,036 阅读7分钟

1. REST-assured 介绍

REST assured 框架被设计用来简化 REST API 的测试和验证,并受 Ruby 和 Groovy 等动态语言中使用的测试框架的较大影响。

REST-assured 有着对 HTTP 的强大的支持,支持 HTTP 的基本的请求方式,除此之外还有一些其他的特性。

在本指南中,我们将认识 REST-assured,并将使用 Hamcrest 进行断言。如果你还不熟悉Hamcrest,可以先看一下这篇文章:使用 Hamcrest 测试

此外,要了解 REST-assured 更深层次的应用,你也可以查看其他文章:

现在,让我们通过一个简单的例子来深入 REST-assured。

2. 第一个 REST-assured 例子

在开始之前,现在测试中导入以下内容:

io.restassured.RestAssured.*
io.restassured.matcher.RestAssuredMatchers.*
org.hamcrest.Matchers.*

我们将使测试尽可能简单,并能够轻松访问到主要 API。 现在,让我们从一个简单的例子开始——一个基本的博彩系统,以下是一些游戏数据:

{
    "id": "390",
    "data": {
        "leagueId": 35,
        "homeTeam": "Norway",
        "visitingTeam": "England",
    },
    "odds": [{
        "price": "1.30",
        "name": "1"
    },
    {
        "price": "5.25",
        "name": "X"
    }]
}

假设这就是我们本地部署的 API http://localhost:8080/events?id=390. 的 JSON 格式的响应数据。

接下来我们使用 REST-assured 来验证 JSON 响应数据中的一些特征。

@Test
public void givenUrl_whenSuccessOnGetsResponseAndJsonHasRequiredKV_thenCorrect() {
   get("/events?id=390").then().statusCode(200).assertThat()
      .body("data.leagueId", equalTo(35)); 
}

上述代码中我们通过调用 /events?id=390 获取到了响应,并断言这个 JSON 响应中的 data.leaguedId 的值为 35。

接下来看一个更有趣的例子,假设你想断言 JSON 响应中的 odds 数组中是否包含 "1.30""5.25" 两个记录。

@Test
public void givenUrl_whenJsonResponseHasArrayWithGivenValuesUnderKey_thenCorrect() {
   get("/events?id=390").then().assertThat()
      .body("odds.price", hasItems("1.30", "5.25"));
}

3. REST-assured 环境搭建

如果你习惯使用 Maven 作为构建工具,可以在 pom.xml 中添加如下依赖:

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>3.3.0</version>
    <scope>test</scope>
</dependency>

想要获取最新版本,可以查看这个链接

REST assured 使用了 Hamcrest 匹配器的来进行断言,因此我们还必须包括 Hamcrest 的依赖:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>2.1</version>
</dependency>

想要获取最新版本,可以查看这个链接

4. 匿名 JSON ROOT 断言

将一个由基本元素而非对象组成的数组:

[1,2,3]

这个数组就被称为匿名的 JSON root,也就是说它没有键值对,但它仍然是有效的 JSON 数据。

在这种情况下,我们可以使用 $ 符号或空字符串(“”)作为路径来进行断言。假设我们通过http://localhost:8080/json 来提供一个服务,那么我们通过如下方式使用 REST-assured 进行断言:

when().get("/json").then().body("$", hasItems(1, 2, 3));

或者这样:

when().get("/json").then().body("", hasItems(1, 2, 3));

5. Floats 和 Doubles

当我们使用 REST-assured 来测试 REST 服务时,我们需要知道 JSON 响应中的 float 浮点数对应的是 float 数据类型。

float 类型的使用不能与 double 类型互换使用,这在 Jav a的许多场景中都是这样。

假设响应内容如下:

{
    "odd": {
        "price": "1.30",
        "ck": 12.2,
        "name": "1"
    }
}

我们需要断言其中的 ck 的值:

get("/odd").then().assertThat().body("odd.ck", equalTo(12.2));

这个测试最终会失败,即使断言的值与响应的值相等。这是因为我们将一个 double 类型和一个 float 类型进行比较。

为了使测试能够执行成功,我们需要将 equalTo 方法的操作数显式指定为浮点类型,如下所示:

get("/odd").then().assertThat().body("odd.ck", equalTo(12.2f));

6.指定请求方式

通常,我们会通过调用一个方法(如get())来执行请求,该方法对应于我们想要使用的请求方法。 此外,我们还可以使用 request() 方法指定 HTTP 请求方式:

@Test
public void whenRequestGet_thenOK(){
    when().request("GET", "/users/eugenp").then().statusCode(200);
}

上述例子等同于直接使用 get() 方法。

同样的,我们可以在请求中发送 HEADCONNECT 和 OPTIONS

@Test
public void whenRequestHead_thenOK() {
    when().request("HEAD", "/users/eugenp").then().statusCode(200);
}

POST请求也遵循类似的语法,我们可以使用 with()body() 方法指定 body。 因此,可以通过发送 POST 请求创建新的 Odd

@Test
public void whenRequestedPost_thenCreated() {
    with().body(new Odd(5.25f, 1, 13.1f, "X"))
      .when()
      .request("POST", "/odds/new")
      .then()
      .statusCode(201);
}

作为请求体发送的 Odd 对象会自动转换为 JSON。我们还可以通过 POST 请求体发送的任何字符串格式数据。

7. 默认配置

我们可以在测试中配置一系列的默认值:

@Before
public void setup() {
    RestAssured.baseURI = "https://api.github.com";
    RestAssured.port = 443;
}

这里我们为请求设置了一个基本 URI 和端口。除此之外,我们还可以配置基本路径、根路径和身份验证信息。

注意:我们还可以使用以下方法重置为标准 REST-assured 的默认值:

RestAssured.reset();

8. 衡量响应时间

接下来我们将通过 time()timeln() 方法来测量响应时间:

@Test
public void whenMeasureResponseTime_thenOK() {
    Response response = RestAssured.get("/users/eugenp");
    long timeInMS = response.time();
    long timeInS = response.timeIn(TimeUnit.SECONDS);
    
    assertEquals(timeInS, timeInMS/1000);
}

注意:

  • time() 方法获取的响应时间的单位是毫秒
  • timeln() 获取的响应时间的单位需要显示指定

8.1 判断响应时间

我们还可以借助简单的长匹配器验证响应时间(以毫秒为单位):

@Test
public void whenValidateResponseTime_thenSuccess() {
    when().get("/users/eugenp").then().time(lessThan(5000L));
}

如果想使用不同的单位验证响应时间,那么我们将使用 time() 方法以及 TimeUnit 参数指定时间单位:

@Test
public void whenValidateResponseTimeInSeconds_thenSuccess(){
    when().get("/users/eugenp").then().time(lessThan(5L),TimeUnit.SECONDS);
}

9. XML 格式响应断言

REST-assured 不仅可以断言 JSON 格式响应,还可以断言 XML 格式响应。

假设一个请求 http://localhost:8080/employees 的响应内容为如下 XML 格式:

<employees>
    <employee category="skilled">
        <first-name>Jane</first-name>
        <last-name>Daisy</last-name>
        <sex>f</sex>
    </employee>
</employees>

我们可以通过如下代码断言 first-name 的值是否为 Jane

@Test
public void givenUrl_whenXmlResponseValueTestsEqual_thenCorrect() {
    post("/employees").then().assertThat()
      .body("employees.employee.first-name", equalTo("Jane"));
}

我们还可以通过将主体匹配器验证所有值是否与预期值匹配,如下所示:

@Test
public void givenUrl_whenMultipleXmlValuesTestEqual_thenCorrect() {
    post("/employees").then().assertThat()
      .body("employees.employee.first-name", equalTo("Jane"))
        .body("employees.employee.last-name", equalTo("Daisy"))
          .body("employees.employee.sex", equalTo("f"));
}

或者使用带变量参数的方式:

@Test
public void givenUrl_whenMultipleXmlValuesTestEqualInShortHand_thenCorrect() {
    post("/employees")
      .then().assertThat().body("employees.employee.first-name", 
        equalTo("Jane"),"employees.employee.last-name", 
          equalTo("Daisy"), "employees.employee.sex", 
            equalTo("f"));
}

10. XPath 断言 XML

我们还可以使用 XPath 断言 XML 响应。如下例子就是断言响应中的 fist-name 属性的属性值:

@Test
public void givenUrl_whenValidatesXmlUsingXpath_thenCorrect() {
    post("/employees").then().assertThat().
      body(hasXPath("/employees/employee/first-name", containsString("Ja")));
}

XPath 还可以使用另一种类似 equalTo 匹配器的方法:

@Test
public void givenUrl_whenValidatesXmlUsingXpath2_thenCorrect() {
    post("/employees").then().assertThat()
      .body(hasXPath("/employees/employee/first-name[text()='Jane']"));
}

11. 记录测试详细信息

11.1 记录请求详情

首先来看如何使用 log().all() 来记录整个请求的日志:

@Test
public void whenLogRequest_thenOK() {
    given().log().all()
      .when().get("/users/eugenp")
      .then().statusCode(200);
}

执行上述代码,将会输出如下日志内容:

Request method:	GET
Request URI:	https://api.github.com:443/users/eugenp
Proxy:			<none>
Request params:	<none>
Query params:	<none>
Form params:	<none>
Path params:	<none>
Multiparts:		<none>
Headers:		Accept=*/*
Cookies:		<none>
Body:			<none>

只记录请求的特定部分,可以 log() 方法与 params()body()headers()cookies()method()path() 结合使用,例如 log().params()

请注意,使用的其他库或过滤器可能会改变实际发送到服务器的内容,因此这只应用于记录初始请求规范。

11.2 记录响应详情

同样地,我们也可以记录响应详情。

如下例子中我们只记录了响应体的日志详情:

@Test
public void whenLogResponse_thenOK() {
    when().get("/repos/eugenp/tutorials")
      .then().log().body().statusCode(200);
}

输出内容为:

{
    "id": 9754983,
    "name": "tutorials",
    "full_name": "eugenp/tutorials",
    "private": false,
    "html_url": "https://github.com/eugenp/tutorials",
    "description": "The \"REST With Spring\" Course: ",
    "fork": false,
    "size": 72371,
    "license": {
        "key": "mit",
        "name": "MIT License",
        "spdx_id": "MIT",
        "url": "https://api.github.com/licenses/mit"
    },
...
}

11.3 记录响应条件触发

我们还可以选择仅在发生错误或状态代码与给定值匹配时记录响应:

@Test
public void whenLogResponseIfErrorOccurred_thenSuccess() {
 
    when().get("/users/eugenp")
      .then().log().ifError();
    when().get("/users/eugenp")
      .then().log().ifStatusCodeIsEqualTo(500);
    when().get("/users/eugenp")
      .then().log().ifStatusCodeMatches(greaterThan(200));
}

11.4 记录断言失败

只有在验证失败时,我们才能记录请求和响应:

@Test
public void whenLogOnlyIfValidationFailed_thenSuccess() {
    when().get("/users/eugenp")
      .then().log().ifValidationFails().statusCode(200);

    given().log().ifValidationFails()
      .when().get("/users/eugenp")
      .then().statusCode(200);
}

在本例中,我们希望验证状态代码是否为200。只有当失败时,才会记录请求和响应。

12. 总结

在本文中,我们了解了 REST-assured 框架,并查看了它最重要的特性,我们可以使用这些特性来测试RESTful 服务并验证它们的响应。

所有这些示例和代码片段的可以在 REST-assured 的 GitHub 中找到。

本文翻译自 www.baeldung.com/rest-assure…