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

前言
何為單元測試
單元測試的目的: 測試當(dāng)前所寫的代碼是否是正確的, 例如輸入一組數(shù)據(jù), 會輸出期望的數(shù)據(jù); 輸入錯誤數(shù)據(jù), 會產(chǎn)生錯誤異常等.
在單元測試中, 我們需要保證被測系統(tǒng)是獨立的(SUT 沒有任何的 DOC), 即當(dāng)被測系統(tǒng)通過測試時, 那么它在任何環(huán)境下都是能夠正常工作的. 編寫單元測試時, 僅僅需要關(guān)注單個類就可以了. 而不需要關(guān)注例如數(shù)據(jù)庫服務(wù), Web 服務(wù)等組件。
背景
進行過JavaWeb開發(fā)的同學(xué)都了解,在進行后臺開發(fā)時不僅需要完成系統(tǒng)功能的開發(fā),為了保證系統(tǒng)的健壯性還要同步編寫對應(yīng)的單元測試類。基于Spring Boot開發(fā)的項目中的test包用于存放單元測試類,同時也提供了對應(yīng)的注解來進行單元測試的編寫,本文結(jié)合Mock對Spring Boot中的單元測試進行總結(jié)。
環(huán)境:JDK1.8+、Spring Boot、mockito。
單元測試的引入
在Spring Boot中引入單元測試只需在pom文件中加入如下依賴,其中提供了JUnit、SpringBoot Test等常見單元測試庫。
<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>
單元測試的創(chuàng)建
每個單元測試類對應(yīng)項目中的一個程序類,每個單元測試方法對應(yīng)程序類中的一個方法,為保證所測試方法的正確性,至少需要設(shè)計四個以上的測試用例,包含:正確用例、錯誤用例和邊界用例。編寫的注釋事項如下:
測試類的位置位于項目test包下,包的層級結(jié)構(gòu)與項目相同; 測試類的命名規(guī)則通常為 xxxTest.java,其中xxx表示待測試類名; 測試類中方法命名規(guī)則為testXxx,其中Xxx表示待測試方法名 ; 測試方法上加上注解 @Test;
話不多說,咱們直接開干。
常用注解
當(dāng)下是注解盛行時代,我們先來了解一下相關(guān)的幾個注解。
| 注解 | 說明 |
|---|---|
@RunWith | 更改測試運行器 , 缺省值org.junit.runner.Runner |
@Before | 初始化方法,執(zhí)行當(dāng)前測試類的每個測試方法前執(zhí)行 |
@Test | 測試方法,在這里可以測試期望異常和超時時間 |
@Test(timeout = 10000) | 超時測試方法,若測試方法未在指定時間內(nèi)結(jié)束則junit自動將其標(biāo)記為失敗 |
@Transactional | 聲明式事務(wù)管理,用于需數(shù)據(jù)庫事務(wù)管理的測試方法 |
@Rollback(true) | 數(shù)據(jù)庫回滾,避免測試數(shù)據(jù)污染數(shù)據(jù)庫 |
相關(guān)理論和技術(shù)點,現(xiàn)在已經(jīng)鋪墊完成,下面,我們使用代碼來實現(xiàn)。
代碼實現(xiàn)
我們分別做三層的測試:controller、service、dao
Service層測試
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class UserServiceTest {
@Autowired
private UserService userService;
/**
* 測試獲取用戶
*/
@Test(timeOut = 300000)
@Transactional
public void testGetUser() {
UserEntity userEntity = userService.findByName("zhangSan");
Assert.assertNotNull(userEntity);
Assert.assertEquals("zhangSan", userEntity.getName());
}
}
是不是很簡單呢?
Controller層測
controller層,也可以稱之為網(wǎng)絡(luò)請求測試。對于網(wǎng)絡(luò)請求進行測試的情形多見于應(yīng)用的Controller層。Spring測試框架提供MockMvc對象,可以在不需要客戶端-服務(wù)端請求的情況下進行Web測試.
測試開始之前需要建立測試環(huán)境,setup方法被@Before修飾。通過MockMvcBuilders工具,創(chuàng)建一個MockMvc對象。
@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層測試
由于DAO層的方法直接操作數(shù)據(jù)庫,為避免測試數(shù)據(jù)對數(shù)據(jù)庫造成污染,使用注解@Transactional和@Rollback在測試完成后對測試數(shù)據(jù)進行回滾。
@RunWith(SpringRunner.class)
@SpringBootTest
public class ScoreControllerTestNew {
@Autowired
private UserDao userDao;
/**
* 測試插入數(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)于三個層面的測試就已經(jīng)搞定了,下面我們來看看,如何使用Mockito模擬數(shù)據(jù)庫操作。
使用Mockito模擬數(shù)據(jù)庫操作
前面在介紹web請求測試時使用了Mock技術(shù),該技術(shù)常用于被測試模塊(方法)依賴于外部系統(tǒng)(web服務(wù)、中間件或是數(shù)據(jù)庫)時。
Mock 的中文譯為仿制的,模擬的,虛假的。對于測試框架來說,即構(gòu)造出一個模擬/虛假的對象,使我們的測試能順利進行下去。
Mockito 是當(dāng)前最流行的 單元測試 Mock 框架。采用 Mock 框架,我們可以 虛擬 出一個 外部依賴,降低測試 組件 之間的 耦合度,只注重代碼的 流程與結(jié)果,真正地實現(xiàn)測試目的。
由于web服務(wù)或數(shù)據(jù)庫不可達時,可以對其進行Mock,在測試時不需要真實的模塊也可完成測試。
常用的Mockito方法如下:
| 方法 | 簡介 |
|---|---|
Mockito.mock(classToMock) | 模擬對象 |
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對DAO層的單元測試進行模擬后,得到的新的單元測試類如下 :
@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),請參考官網(wǎng):https://site.mockito.org/
后記
本文重在用代碼案例講解單元測試,篇幅有限,先分享到這里,如有不當(dāng)之處,敬請諒解指出。
干貨分享
最近將個人學(xué)習(xí)筆記整理成冊,使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤地址,無套路領(lǐng)?。?/p>
?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ū)動設(shè)計速成)》?007:全部?008:加技術(shù)群討論
加個關(guān)注不迷路
喜歡就點個"在看"唄^_^
