在前面的文章,從建立 Controller、Service,到串接資料庫,都是使用 Postman 來觀察程式的執行結果。然而隨著專案規模擴大,像這樣手動測試會非常麻煩。本文就讓我們來撰寫整合測試,利用程式來測試現有的功能。
本文的練習用專案:https://github.com/ntub46010/SpringBootTutorial/tree/Ch8
一、測試的目的
測試的目的是確認功能可以正常運作,例如先前對後端 API 的測試是透過 Postman。然而逐一測試會很費時,因此需要寫程式來自動化。
每當開發新功能、修復 Bug 後,我們通常需要針對該次的改動撰寫測試程式,並與舊有的測試一起執行。除了證明該次交付的程式可以正常運作或能解決問題,也要確保不會影響現有的功能。
本文所要講解的是「整合測試」(integration test),並非「單元測試」(unit test)。整合測試會依賴於外部的服務,比方說我們串接的 MongoDB 資料庫就是一種外部服務,測試時一樣會串接。而單元測試會將外部服務用「模擬物件」(Mock)的方式來替代,但不在本文的範疇。
二、建立測試類別
本文會用到 Spring Boot 提供的「MockMvc」元件。它能針對當前專案模擬出發送 HTTP 請求的動作,並取得狀態碼、回應標頭(response header)與主體(response body)等結果,跟 Postman 很像。此外也可以檢查回應中的內容是否如開發者所預期,例如有幾筆資料、某個 JSON 欄位值為多少。
為了撰寫整合測試,請在 pom.xml 檔案匯入測試函式庫。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
首先在專案的 src\test\java 路徑中建立一個測試類別叫做 ProductTest,並且 package 要跟 src\main\java 相同(筆者的範例專案是 com.vincent.demo)。它會存放用來測試 Product 功能的程式。
接著在類別加上三個標記。@RunWith 和 @SpringBootTest 是定義測試程式要在 Spring Boot 的環境下執行。@AutoConfigureMockMvc 代表測試開始時會在元件容器中建立 MockMvc 元件,請讀者注入進來。
package com.vincent.demo;
// 略過 import
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ProductTest {
@Autowired
private MockMvc mockMvc;
}
測試程式和原本業務邏輯的程式一樣,也有自己的環境參數配置檔。因此請在 src\test 路徑下新增「resources」資料夾,建立「application.properties」檔案。並添加測試程式專用的資料庫專屬位址。請不要與開發中程式使用相同的資料庫,避免互相影響。
spring.data.mongodb.uri=mongodb://localhost:27017/testing
筆者的公司則設定成每位同事都使用自己的資料庫,如下:
# ${USERNAME} 會自動帶入電腦的使用者名稱
spring.data.mongodb.uri=mongodb://192.168.xxx.xxx:27017/${USERNAME}
三、撰寫測試案例
測試類別準備好後,就能撰寫測試程式了。本文以新增產品的情境為例,請宣告一個方法,加上 @Test 標記,代表這是要被執行的測試程式,其正式名稱為「測試案例」(test case)。
public class ProductTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testCreateProduct() throws Exception {
}
}
為了進行測試,下面會準備一些有關請求的資料,包含請求標頭與主體、HTTP 方法及目標 API。其實在操作 Postman 時都認識過了,只是這邊改為透過程式碼來完成。
使用 Postman 時會在「Body」頁籤選擇「raw」與「JSON」,再輸入 JSON 字串作為請求主體,因此在「Headers」頁籤能發現「Content-Type」的資料。
在測試程式中同樣會準備這些資料,我們以 HttpHeaders 與 JSONObject 提供。
@Test
public void testCreateProduct() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
JSONObject request = new JSONObject()
.put("name", "Harry Potter")
.put("price", 450);
// TODO
}
上面的 HttpHeaders 物件是用來存放請求標頭的資料,此處存放了 Content-Type: application/json 這項資料。而 HttpHeaders 與 MediaType 類別中正好有這些字串常數,於是筆者便拿來利用。
綜合以上資料,我們使用 MockMvcRequestBuilders 類別,透過 builder 的方式來建立請求。最後得到 MockHttpServletRequestBuilder 物件,它是 RequestBuilder 介面的物件。@Test
public void testCreateProduct() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
JSONObject request = new JSONObject()
.put("name", "Harry Potter")
.put("price", 450);
RequestBuilder requestBuilder =
MockMvcRequestBuilders
.post("/products")
.headers(httpHeaders)
.content(request.toString());
// TODO
}
上面用了三個方法。post 方法代表發送 POST 請求,參數需傳入 API 路徑。headers 方法可附加請求標頭。content 方法可加入請求主體,此處傳入 JSON 字串作為參數。
四、發出請求與驗證
請求資料建立完成後,就能透過 MockMvc 元件來發出模擬請求。只要透過 perform 方法,傳入上一節建立的請求資料即可。perform 方法的參數接受 RequestBuilder 介面的物件。
@Test
public void testCreateProduct() throws Exception {
// 其餘略過
mockMvc.perform(requestBuilder)
.andDo(print())
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").hasJsonPath())
.andExpect(jsonPath("$.name").value(request.getString("name")))
.andExpect(jsonPath("$.price").value(request.getInt("price")))
.andExpect(header().exists(HttpHeaders.LOCATION))
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));
}
請求發出後,隨即透過 andExpect 方法進行回應資料的驗證。以下介紹在範例中使用到的驗證方式。
- status().isCreated():驗證 HTTP 狀態碼應為201。讀者可自行探索其他狀態,如 isOk、isNotFound 等。或透過 is 方法直接傳入狀態碼。
- jsonPath():獲取指定 JSON 欄位的值。以「$」符號開始,使用「.」符號前往下一層的路徑。
- hasJsonPath():驗證某個 JSON 欄位存在。
- value():驗證某個 JSON 欄位值為何。
- header().exists():驗證回應標頭中的某欄位存在。
- header().string():驗證回應標頭中的某欄位值為何。
使用 andDO(print()) 方法,能將測試程式的請求與回應詳情印在 Console 視窗。
這樣就寫好一個測試程式了。在程式碼左邊的行數區塊,找到綠色三角形,按下去便能執行。下方的 Console 視窗會顯示一些資料,例如回應內容。而左邊的綠色勾勾代表測試通過,橘色代表驗證失敗,紅色代表發生例外。
若一個測試案例中,遇到某一個驗證未通過,那程式就不會繼續往下執行。讀者應根據 Console 視窗內的訊息,找出未通過的原因。可能是業務邏輯或測試程式有問題。
而若要執行多個測試案例,請按下測試類別名稱旁邊的綠色三角形。或是在 IntelliJ 最左邊的專案檢視視窗,選取一到多個測試類別,按右鍵一次執行。
本文的完成範例:
https://github.com/ntub46010/SpringBootTutorial/tree/Ch9
留言
張貼留言