iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 20
1
Modern Web

站在Web前端人員角度,學習 Spring Boot 後端開發系列 第 20

Day 20 - Spring Boot 組起來再測一遍-整合測試

聰明是一種天賦,而善良是一種選擇。天賦得來很容易,而選擇則頗為不易。 - 傑夫.貝佐斯

整合測試

整合測試是對程式採用一次性或增值方式整合起來,對系統進行正確性結果驗證的測試工作。 -維基百科

在前面文章我們有提到單元測試,即在系統每個單一元件都進行功能或邏輯測試,而整合測試則是要確認各個元件組合在一起運作是否有異常問題發生。

單元測試整合測試唯一不同點是有無真正呼叫依賴系統(外部服務),由於單元測試是最小單位測試,所以會將依賴的系統模擬(Mock)出一個假的對象,以便隔離外部,而整合測試就是會測試依賴系統結合後的結果。

測試CRUD RESTful整個過程是否有如預期一樣運作,Java 提供了一個In-memory的測試資料庫H2,若我們今天是介接MySQL這樣要執行整合測試就顯得困難許多,必須安裝並運行MySQL保持連接且要設置正確得用戶與資料庫 。H2 Database就是一個很好的測試工具,由於資料是內存,測試程序結束後資料會不留痕跡地被刪除,以完全獨立的方式運行。

https://ithelp.ithome.com.tw/upload/images/20200929/20118857vjl4lIQn27.png

(1)配置h2 database pom.xml

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

(2)準備一個sql檔案,想要在執行測試載入此data,在src / main / resources 放入此sql檔,如下圖。
https://ithelp.ithome.com.tw/upload/images/20200929/201188571ICSGnnPKZ.png

data.sql 檔案,一個新增TODO的sql指令

INSERT INTO TODO (ID,TASK, STATUS, CREATE_TIME, UPDATE_TIME) values (1, '洗衣服', 1, '2020-09-20 19:00:00', '2020-09-20 19:00:00' );

(3)一樣在src / test / 新增一個檔案為TestToDoController.java ,一開始先在類別上標註:
@Sql用於註釋測試類或測試方法以配置SQL。
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) 在類中別的每個測試方法之前清除緩存
@AutoConfigureMockMvc 注入用MockMvc 模擬HTTP請求

@SpringBootTest
@AutoConfigureMockMvc
@Sql(scripts = "classpath:test/data.sql") // sql 檔案放置的地方
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) // 在類中別的每個測試方法之前
public class TestToDoController {
    @Autowired
    private MockMvc mockMvc;
    @Autowired
    TodoService todoService;
    @Autowired
    ObjectMapper objectMapper;
}

(4)開始測試CRUD API,每執行測試方法之前,都會先插入一筆資料(data.sql)
測試 [GET] /api/todos

@Test
public void testGetTodos() throws Exception {
    String strDate = "2020-09-20 19:00:00";
    DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date date = format.parse(strDate);

    // [Arrange] 預期回傳的值
    List<Todo> expectedList = new ArrayList();
    Todo todo = new Todo();
    todo.setId(1);
    todo.setTask("洗衣服");
    todo.setCreateTime(date);
    todo.setUpdateTime(date);
    expectedList.add(todo);

    // [Act] 模擬網路呼叫[GET] /api/todos
    String returnString = mockMvc.perform(MockMvcRequestBuilders.get("/api/todos")
            .accept(MediaType.APPLICATION_JSON ))
            .andExpect(status().isOk())
            .andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);

    Iterable<Todo> actualList = objectMapper.readValue(returnString, new TypeReference<Iterable<Todo>>() {
    });
    // [Assert] 判定回傳的body是否跟預期的一樣
    assertEquals(expectedList,  actualList);
}

測試 [POST] /api/todos

@Test
public void testCreateTodos() throws Exception {
    // [Arrange] 預期回傳的值
    JSONObject todoObject = new JSONObject();
    todoObject.put("task", "寫文章");

    // [Act] 模擬網路呼叫[POST] /api/todos
    String actualId = mockMvc.perform(MockMvcRequestBuilders.post("/api/todos")
            .accept(MediaType.APPLICATION_JSON) //response 設定型別
            .contentType(MediaType.APPLICATION_JSON) // request 設定型別
            .content(String.valueOf(todoObject))) // body 內容
            .andExpect(status().isCreated()) // 預期回應的status code 為 201(Created)
            .andReturn().getResponse().getContentAsString();

    // [Assert] 判定回傳的body是否跟預期的一樣
    assertEquals(2,  Integer.parseInt(actualId));
}

測試 [PUT] /api/todos/1 資料庫存在的一筆資料

@Test
public void testUpdateTodoSuccess() throws Exception {
    JSONObject todoObject = new JSONObject();
    todoObject.put("status", 2);

    // [Act] 模擬網路呼叫[PUT] /api/todos/{id}
    mockMvc.perform(MockMvcRequestBuilders.put("/api/todos/1")
            .contentType(MediaType.APPLICATION_JSON) // request 設定型別
            .content(String.valueOf(todoObject))) // body 內容
            .andExpect(status().isOk()); // [Assert] 預期回應的status code 為 200(OK)
}

測試 [PUT] /api/todos/100資料庫不存在此資料

@Test
public void testUpdateTodoButIdNotExist() throws Exception {
  JSONObject todoObject = new JSONObject();
  todoObject.put("status", 2);

  // [Act] 模擬網路呼叫[PUT] /api/todos/{id}
  mockMvc.perform(MockMvcRequestBuilders.put("/api/todos/100")
          .contentType(MediaType.APPLICATION_JSON) // request 設定型別
          .content(String.valueOf(todoObject))) // body 內容
          .andExpect(status().isBadRequest()); // [Assert] 預期回應的status code 為 400(Bad Request)
}

測試 [DELETE] /api/todos/1資料庫存在的一筆資料

@Test
public void testDeleteTodoSuccess() throws Exception {
    // [Act] 模擬網路呼叫[DELETE] /api/todos/{id}
    mockMvc.perform(MockMvcRequestBuilders.delete("/api/todos/1")
            .contentType(MediaType.APPLICATION_JSON)) // request 設定型別
            .andExpect(status().isNoContent()); // [Assert] 預期回應的status code 為 204(No Content)
}

測試 [DELETE] /api/todos/100 資料庫不存在此筆資料

@Test
public void testDeleteTodoButIdNotExist() throws Exception {
    // [Act] 模擬網路呼叫[DELETE] /api/todos/{id}
    mockMvc.perform(MockMvcRequestBuilders.delete("/api/todos/100")
            .contentType(MediaType.APPLICATION_JSON)) // request 設定型別
            .andExpect(status().isBadRequest()); // [Assert] 預期回應的status code 為 400(Bad Request)
}

最後結果 ((灑花~~
https://ithelp.ithome.com.tw/upload/images/20200929/2011885732HAVpAOUc.png

程式同步於Github


上一篇
Day 19 - Spring Boot HTTP的請求也可以模擬測試?使用MockMvc
下一篇
Day 21 - Spring Boot Swagger API 文件神器
系列文
站在Web前端人員角度,學習 Spring Boot 後端開發30

尚未有邦友留言

立即登入留言