集成測試
集成測試是指項目代碼在單元測試完成后進行的第二階段測試。集成測試的重點是在集成組件或單元之間交互時暴露缺陷,以保證不同模塊之間相互調用的正確性。在Spring Boot的項目集成測試中,將測試Controller和Dao的完整請求處理。應用程序在服務器中運行,以創建應用程序上下文和所有的Bean,其中有些Bean可能會被覆蓋以模擬某些行為。進行集成測試,是為了保證項目能夠達到下面的目的:
降低項目中發生錯誤的風險;
驗證接口的功能是否符合設計的初衷;
測試接口是否可用;
查找項目中的Bug并進行修復。
在項目開發中,需要開展集成測試的情況有以下4種:
單個模塊由開發人員設計,而多個模塊之間的設計可能不盡相同;
檢查多個模塊與數據庫是否正確連通;
驗證不同模塊之間是否存在不兼容或錯誤的情況;
驗證不同模塊之間的異常和錯誤處理。
集成測試自動配置
Spring Boot框架支持集成測試,項目開發時只需要做簡單的配置就可以完成集成測試。本節將在7.1節的基礎上介紹項目配置后進行集成測試的過程。
首先在pom.xml中添加項目依賴:
<parent>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>junit5-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>junit5-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--加入JUnit5進行測試;如果想用JUnit4進行測試,則把exclusions去除-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.11.2</version>
</dependency>
</dependencies>@SpringBootTest是Spring Boot提供的一個注解,表示當前類是SpringBoot環境中的一個測試類。如果使用的是JUnit 4,則需要用到@RunWith(SpringRunner.class)和@Spring-BootTest注解。但是在JUnit 5中,只需要使用@SpringBootTest注解就可以了。
測試Spring MVC入口
下面使用Spring中的集成測試模塊來測試項目入口Controller的方法。
(1)新建GoodsController類,并新建要測試的業務代碼,代碼如下:
package com.example.junit5demo.controller;
import com.example.junit5demo.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
public class GoodsController {
@Autowired
private GoodsService goodsService;
@GetMapping("/queryGood")
public String queryGood(@RequestParam("name") String name) {
goodsService.queryGood(name);
return "queryGood " + name;
}
@PostMapping("/countGood")
public String countGood(@RequestBody Goods goods) {
goodsService.countGood(goods.getName());
return "countGood " + goods;
}
}
(2)新建Goods的實體類,代碼如下:
package com.example.junit5demo.controller;
import lombok.Data;
@Data
public class Goods {
private long id;
private String name;
private int status;
}
(3)新建Goods的service和實現類,代碼如下:
package com.example.junit5demo.service;
public interface GoodsService {
void queryGood(String name);
void countGood(String name);
}
Goods的實現類代碼如下:
package com.example.junit5demo.service;
import org.springframework.stereotype.Service;
@Service
public class GoodsServiceImpl implements GoodsService {
@Override public void queryGood(String name) {
System.out.println("執行了goods的queryGood方法,參數:" + name);
}
@Override
public void countGood(String name) {
System.out.println("執行了goods的countGood方法,參數:" + name);
}
}
(4)編寫Controller的集成測試用例,代碼如下:
package com.example.junit5demo.controller;
import lombok.extern.SLF4J.SLF4J;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.Auto
ConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import
org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import
org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import
org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@Slf4j
@SpringBootTest
@AutoConfigureMockMvc
class GoodsControllerTest {
private MockHttpSession session; @Autowired
private MockMvc mvc;
@BeforeEach
public void setupMockMvc() {
//設置MVC
session = new MockHttpSession();
}
@Test
void queryGood() throws Exception {
MvcResult mvcResult = (MvcResult) mvc.perform(MockMvcRequest
Builders.get("/queryGood")
.accept(MediaType.ALL)
.session(session)
.param("name", "cc")
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
//得到返回代碼
int status = mvcResult.getResponse().getStatus();
//得到返回結果
String result = mvcResult.getResponse().getContentAsString();
log.info("status是:{},內容是:{}", status, result);
}
@Test
void countGood() throws Exception {
String body = "{\"id\":1,\"name\":\"cc\",\"status\":2}";
MvcResult mvcResult = (MvcResult) mvc.perform(MockMvcRequest
Builders.post("/countGood")
.accept(MediaType.ALL)
.session(session)
.content(body)
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn(); //得到返回代碼
int status = mvcResult.getResponse().getStatus();
//得到返回結果
String result = mvcResult.getResponse().getContentAsString();
log.info("status是:{},內容是:{}", status, result);
}
}
(5)運行第一個測試用例,在控制臺上可以看到測試用例的輸出結果,它執行了queryGood的GET方法,實際調用了代碼中的方法,且返回了預期的結果。
執行了goods的queryGood方法,參數:cc
MockHttpServletRequest:
HTTP Method = GET
Request URI = /queryGood
Parameters = {name=[cc]}
Headers = [Accept:"*/*"]
Body = null
Session Attrs = {}
Handler:
Type = com.example.junit5demo.controller.GoodsController
Method = com.example.junit5demo.controller.GoodsController
#queryGood(String)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"text/plain;charset=UTF-8", Content
Length:"12"]
Content type = text/plain;charset=UTF-8
Body = queryGood cc
Forwarded URL = null
Redirected URL = null
Cookies = []
2021-07-10 15:51:30.786 INFO 15428 --- [ main]
c.e.j.controller.
GoodsControllerTest : status是:200,內容是:queryGood cc
2021-07-10 15:51:30.808 INFO 15428 --- [extShutdownHook]
o.s.s.concurrent.
ThreadPoolTaskExecutor : Shutting down ExecutorService 'application
TaskExecutor'
(6)執行第二個測試用例的方法,并執行countGood的POST方法,當前POST使用的是application/json方式,需要單獨設置,通過日志可以看到已經請求成功。
執行了goods的countGood方法,參數:cc
MockHttpServletRequest:
HTTP Method = POST
Request URI = /countGood
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8",
Accept:"*/*", Content-Length:"31"]
Body = {"id":1,"name":"cc","status":2}
Session Attrs = {}Handler:
Type = com.example.junit5demo.controller.GoodsController
Method = com.example.junit5demo.controller.GoodsController#
countGood(Goods)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"text/plain;charset=UTF-8", Content
Length:"40"]
Content type = text/plain;charset=UTF-8
Body = countGood Goods(id=1, name=cc, status=2)
Forwarded URL = null
Redirected URL = null
Cookies = []
2021-07-10 15:54:18.096 INFO 15720 --- [ main]
c.e.j.controller.
GoodsControllerTest : status是:200,內容是:countGood Goods(id=1,
name=cc, status=2)
2021-07-10 15:54:18.119 INFO 15720 --- [extShutdownHook]
o.s.s.concurrent.
ThreadPoolTaskExecutor : Shutting down ExecutorService 'application
TaskExecutor'
通過以上的集成測試,完成了API從入口開始的全鏈路方法調用,驗證了所有的方法調用,并完成了業務代碼的測試,至此完成了Controller的集成測試過程。