小白都能看懂的 Spring Boot 單元測(cè)試
點(diǎn)擊下方“IT牧場(chǎng)”,選擇“設(shè)為星標(biāo)”

前言
何為單元測(cè)試
單元測(cè)試的目的: 測(cè)試當(dāng)前所寫的代碼是否是正確的, 例如輸入一組數(shù)據(jù), 會(huì)輸出期望的數(shù)據(jù); 輸入錯(cuò)誤數(shù)據(jù), 會(huì)產(chǎn)生錯(cuò)誤異常等.
在單元測(cè)試中, 我們需要保證被測(cè)系統(tǒng)是獨(dú)立的(SUT 沒有任何的 DOC), 即當(dāng)被測(cè)系統(tǒng)通過測(cè)試時(shí), 那么它在任何環(huán)境下都是能夠正常工作的. 編寫單元測(cè)試時(shí), 僅僅需要關(guān)注單個(gè)類就可以了. 而不需要關(guān)注例如數(shù)據(jù)庫(kù)服務(wù), Web 服務(wù)等組件。
背景
進(jìn)行過JavaWeb開發(fā)的同學(xué)都了解,在進(jìn)行后臺(tái)開發(fā)時(shí)不僅需要完成系統(tǒng)功能的開發(fā),為了保證系統(tǒng)的健壯性還要同步編寫對(duì)應(yīng)的單元測(cè)試類。基于Spring Boot開發(fā)的項(xiàng)目中的test包用于存放單元測(cè)試類,同時(shí)也提供了對(duì)應(yīng)的注解來進(jìn)行單元測(cè)試的編寫,本文結(jié)合Mock對(duì)Spring Boot中的單元測(cè)試進(jìn)行總結(jié)。
環(huán)境:JDK1.8+、Spring Boot、mockito。
單元測(cè)試的引入
在Spring Boot中引入單元測(cè)試只需在pom文件中加入如下依賴,其中提供了JUnit、SpringBoot Test等常見單元測(cè)試庫(kù)。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.0.111-beta</version>
</dependency>
單元測(cè)試的創(chuàng)建
每個(gè)單元測(cè)試類對(duì)應(yīng)項(xiàng)目中的一個(gè)程序類,每個(gè)單元測(cè)試方法對(duì)應(yīng)程序類中的一個(gè)方法,為保證所測(cè)試方法的正確性,至少需要設(shè)計(jì)四個(gè)以上的測(cè)試用例,包含:正確用例、錯(cuò)誤用例和邊界用例。編寫的注釋事項(xiàng)如下:
測(cè)試類的位置位于項(xiàng)目test包下,包的層級(jí)結(jié)構(gòu)與項(xiàng)目相同; 測(cè)試類的命名規(guī)則通常為 xxxTest.java,其中xxx表示待測(cè)試類名; 測(cè)試類中方法命名規(guī)則為testXxx,其中Xxx表示待測(cè)試方法名 ; 測(cè)試方法上加上注解 @Test;
話不多說,咱們直接開干。
常用注解
當(dāng)下是注解盛行時(shí)代,我們先來了解一下相關(guān)的幾個(gè)注解。
| 注解 | 說明 |
|---|---|
@RunWith | 更改測(cè)試運(yùn)行器 , 缺省值org.junit.runner.Runner |
@Before | 初始化方法,執(zhí)行當(dāng)前測(cè)試類的每個(gè)測(cè)試方法前執(zhí)行 |
@Test | 測(cè)試方法,在這里可以測(cè)試期望異常和超時(shí)時(shí)間 |
@Test(timeout = 10000) | 超時(shí)測(cè)試方法,若測(cè)試方法未在指定時(shí)間內(nèi)結(jié)束則junit自動(dòng)將其標(biāo)記為失敗 |
@Transactional | 聲明式事務(wù)管理,用于需數(shù)據(jù)庫(kù)事務(wù)管理的測(cè)試方法 |
@Rollback(true) | 數(shù)據(jù)庫(kù)回滾,避免測(cè)試數(shù)據(jù)污染數(shù)據(jù)庫(kù) |
相關(guān)理論和技術(shù)點(diǎn),現(xiàn)在已經(jīng)鋪墊完成,下面,我們使用代碼來實(shí)現(xiàn)。
代碼實(shí)現(xiàn)
我們分別做三層的測(cè)試:controller、service、dao
Service層測(cè)試
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class UserServiceTest {
@Autowired
private UserService userService;
/**
* 測(cè)試獲取用戶
*/
@Test(timeOut = 300000)
@Transactional
public void testGetUser() {
UserEntity userEntity = userService.findByName("zhangSan");
Assert.assertNotNull(userEntity);
Assert.assertEquals("zhangSan", userEntity.getName());
}
}
是不是很簡(jiǎn)單呢?
Controller層測(cè)
controller層,也可以稱之為網(wǎng)絡(luò)請(qǐng)求測(cè)試。對(duì)于網(wǎng)絡(luò)請(qǐng)求進(jìn)行測(cè)試的情形多見于應(yīng)用的Controller層。Spring測(cè)試框架提供MockMvc對(duì)象,可以在不需要客戶端-服務(wù)端請(qǐng)求的情況下進(jìn)行Web測(cè)試.
測(cè)試開始之前需要建立測(cè)試環(huán)境,setup方法被@Before修飾。通過MockMvcBuilders工具,創(chuàng)建一個(gè)MockMvc對(duì)象。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
class UserControllerTest {
@Autowired
private UserController userController ;
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setup(){
mockMvc = MockMvcBuilders.standaloneSetup(userController).build;
}
/**
* 獲取用戶列表
*/
@Test(timeOut = 300000)
public void testGetUserList() throws Exception {
String url = "/user/getUserList";
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(url))
.andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
Assert.assertNotNull(mvcResult);
}
}
DAO層測(cè)試
由于DAO層的方法直接操作數(shù)據(jù)庫(kù),為避免測(cè)試數(shù)據(jù)對(duì)數(shù)據(jù)庫(kù)造成污染,使用注解@Transactional和@Rollback在測(cè)試完成后對(duì)測(cè)試數(shù)據(jù)進(jìn)行回滾。
@RunWith(SpringRunner.class)
@SpringBootTest
public class ScoreControllerTestNew {
@Autowired
private UserDao userDao;
/**
* 測(cè)試插入數(shù)據(jù)
*/
@Test
@Rollback(value = true)
@Transactional
public void testInsert() {
User userZhang = new User();
userZhang.setName("zhangSan");
userZhang.setAge(23);
userZhang.setGender(0);
userZhang.setEmail("[email protected]");
int n = userDao.insert(userZhang);
Assert.assertEquals(1, n);
}
}
到此,關(guān)于三個(gè)層面的測(cè)試就已經(jīng)搞定了,下面我們來看看,如何使用Mockito模擬數(shù)據(jù)庫(kù)操作。
使用Mockito模擬數(shù)據(jù)庫(kù)操作
前面在介紹web請(qǐng)求測(cè)試時(shí)使用了Mock技術(shù),該技術(shù)常用于被測(cè)試模塊(方法)依賴于外部系統(tǒng)(web服務(wù)、中間件或是數(shù)據(jù)庫(kù))時(shí)。
Mock 的中文譯為仿制的,模擬的,虛假的。對(duì)于測(cè)試框架來說,即構(gòu)造出一個(gè)模擬/虛假的對(duì)象,使我們的測(cè)試能順利進(jìn)行下去。
Mockito 是當(dāng)前最流行的 單元測(cè)試 Mock 框架。采用 Mock 框架,我們可以 虛擬 出一個(gè) 外部依賴,降低測(cè)試 組件 之間的 耦合度,只注重代碼的 流程與結(jié)果,真正地實(shí)現(xiàn)測(cè)試目的。
由于web服務(wù)或數(shù)據(jù)庫(kù)不可達(dá)時(shí),可以對(duì)其進(jìn)行Mock,在測(cè)試時(shí)不需要真實(shí)的模塊也可完成測(cè)試。
常用的Mockito方法如下:
| 方法 | 簡(jiǎn)介 |
|---|---|
Mockito.mock(classToMock) | 模擬對(duì)象 |
Mockito.when(methodCall).thenReturn(value) | 參數(shù)匹配 |
Mockito.doReturn(toBeReturned).when(mock).[method] | 參數(shù)匹配(直接執(zhí)行不判斷) |
Mockito.when(methodCall).thenAnswer(answer)) | 預(yù)期回調(diào)接口生成期望值 |
Mockito.doNothing().when(mock).[method] | 不做任何返回 |
在使用Mockito對(duì)DAO層的單元測(cè)試進(jìn)行模擬后,得到的新的單元測(cè)試類如下 :
@RunWith(SpringRunner.class)
public class UserDaoTest {
@MockBean
private UserDao userDao;
private User userZhang = new User();
userZhang.setName("zhangSan");
userZhang.setAge(23);
@Before
public void setup() {
Mockito.when(userDao.findByName("zhangSan")).willReturn(userZhang);
Mockito.when(userDao.findByName("liSi")).willReturn(null);
}
@Test
public void testGetUser() {
Assert.assertEquals(userZhang, userDao.findByName("zhangSan"));
Assert.assertEquals(null, userDao.findByName("liSi"));
}
}
關(guān)于mockito相關(guān),請(qǐng)參考官網(wǎng):https://site.mockito.org/
后記
本文重在用代碼案例講解單元測(cè)試,篇幅有限,先分享到這里,如有不當(dāng)之處,敬請(qǐng)諒解指出。
干貨分享
最近將個(gè)人學(xué)習(xí)筆記整理成冊(cè),使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤地址,無套路領(lǐng)取!
?001:《Java并發(fā)與高并發(fā)解決方案》學(xué)習(xí)筆記;?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學(xué)習(xí)筆記;?003:《Java面試寶典》?004:《Docker開源書》?005:《Kubernetes開源書》?006:《DDD速成(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)速成)》?007:全部?008:加技術(shù)群討論
加個(gè)關(guān)注不迷路
喜歡就點(diǎn)個(gè)"在看"唄^_^
