1. 引言
REST-assured 的設計目標是簡化 REST API 的測試和驗證,並且深受動態語言(如 Ruby 和 Groovy)中使用的測試技術的影響。
該庫對 HTTP 提供了強大的支持,當然包括 HTTP 動詞和標準 HTTP 操作,同時也遠不止這些基礎內容。
在本指南中,我們將探索 REST-assured,並使用 Hamcrest 進行斷言。如果您不熟悉 Hamcrest,請先閲讀:使用 Hamcrest 進行測試教程。
此外,要了解 REST-assured 的更高級用法,請查看我們的其他文章:
- 使用 Groovy 進行 REST-assured
- 使用 REST-assured 進行 JSON Schema 驗證
- REST-assured 中的參數、頭和 Cookie
現在,讓我們通過一個簡單的示例開始吧。
2. 簡單示例測試
在開始之前,讓我們確保我們的測試包含以下靜態導入:
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"
}]
}假設這是通過向 http://localhost:8080/events?id=390 — 本地部署的API發送請求獲得的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 字符串 的響應體,其中 leagueId 的 數據 對象值為 35。
下面我們來看一個更具代表性的例子。假設我們想要驗證 賠率數組中包含價格為 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,我們在 rest-assured 依賴項中在 pom.xml 文件中:
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>REST-assured 利用 Hamcrest 匹配器執行斷言,因此我們還需要包含 hamcrest 依賴項:
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.1</version>
</dependency>4. 匿名 JSON 根驗證
考慮一個由基本數據類型組成而不是對象組成的數組:
[1, 2, 3]這被稱為匿名 JSON 根,意味着它沒有鍵值對,但仍然是有效的 JSON 數據。
在這種情況下,我們可以使用 $ 符號或空 String (“”) 作為路徑進行驗證。假設我們通過 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. 浮點數和雙精度浮點數
當我們使用 REST-assured 測試我們的 REST 服務時,需要理解的是,JSON 響應中的浮點數會被映射到原始類型 float。
float 類型的用法與 double 類型不互換:
{
"odd": {
"price": "1.30",
"ck": 12.2,
"name": "1"
}
}如果我們將以下測試應用於 ck 的值:
get("/odd").then().assertThat().body("odd.ck", equalTo(12.2));這個測試即使測試值等於響應中的值也可能失敗。這是因為我們正在將值與double進行比較,而不是與float進行比較。
為了使它正常工作,我們必須明確地將操作數指定給equalTo匹配器方法,並將其指定為float。
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()
同樣,我們可以發送 HEAD, CONNECT, 和 OPTIONS 請求:
@Test
public void whenRequestHead_thenOK() {
when().request("HEAD", "/users/eugenp").then().statusCode(200);
}POST 請求也遵循類似的語法,我們可以通過使用 with() 和 body() 方法指定請求體。
因此,要創建一個新的奇數,通過發送POST 請求,我們可以運行:
@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。 此外,我們還可以將任何想要發送的 String 作為我們的 POST body 發送。
7. 默認值配置
我們可以配置大量的默認值供測試使用:
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://api.github.com";
RestAssured.port = 443;
}在這裏,我們設置了請求的基URI和端口。除此之外,我們還可以配置基本路徑、根路徑和身份驗證。
請注意,我們也可以通過使用以下方法重置為標準 REST-assured 默認值:
RestAssured.reset();8. 測量響應時間
讓我們看看如何使用 Response 對象中的 time() 和 timeIn() 方法來 測量響應時間:
@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() 用於獲取響應時間(毫秒)
- timeIn() 用於獲取響應時間,指定時間單位
8.1. 驗證響應時間
我們可以通過使用一個簡單的 long Matcher 來驗證響應時間,單位為毫秒。
@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 響應驗證
它不僅可以驗證 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 for XML
我們可以使用 XPath 驗證我們的響應。 考慮一個示例,它在 first-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. 日誌測試詳情
This section details the logging test setup and results. It covers the configuration of the logging system, the types of events being logged, and the analysis performed on the collected logs.
Logging System Configuration
The logging system was configured using the following parameters:
- Log Level: DEBUG
- Log Format: JSON
- Log Destination:
/var/log/myapp.log - Log Rotation Policy: Daily rotation with a maximum file size of 100MB.
Logged Events
The following events were configured to be logged:
- User login attempts
- Database query execution times
- Error conditions (e.g., exceptions, warnings)
- System resource usage (CPU, memory)
Log Analysis
The collected logs were analyzed using Logstash to identify trends and anomalies. The analysis revealed several potential issues, including:
- High CPU usage during peak hours.
- Slow query execution times for certain database queries.
- Numerous warnings related to invalid input data.
11.1. 記錄請求詳情
首先,讓我們看看如何使用 <em><strong>log().all()</strong></em> 方法記錄完整的請求詳情:
@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>為了僅記錄請求的特定部分,而不是使用 <em all()</em>,我們可以調用 <em params()</em>、<em body()</em>、<em headers()</em>、<em cookies()</em>、<em method()</em>、<em path()</em>。
@Test
public void whenLogRequest_thenOK() {
given().log().params()
.when().get("/users/eugenp")
.then().statusCode(200);
}請注意,其他使用的庫或過濾器可能會改變實際發送到服務器的內容,因此此方法僅用於記錄初始請求規範。
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 服務及其響應的其最重要的功能。