小白搞 Spring Boot 單元測試
大家好,我是田維常,今天給大家分享來自于一位小伙的投稿。
內(nèi)容是:Spring Boot 中的單元測
前言
何為單元測試
單元測試的目的: 測試當(dāng)前所寫的代碼是否是正確的, 例如輸入一組數(shù)據(jù), 會輸出期望的數(shù)據(jù); 輸入錯誤數(shù)據(jù), 會產(chǎn)生錯誤異常等. 在單元測試中, 我們需要保證被測系統(tǒng)是獨(dú)立的(SUT 沒有任何的 DOC), 即當(dāng)被測系統(tǒng)通過測試時, 那么它在任何環(huán)境下都是能夠正常工作的. 編寫單元測試時, 僅僅需要關(guān)注單個類就可以了. 而不需要關(guān)注例如數(shù)據(jù)庫服務(wù), Web 服務(wù)等組件。
背景
進(jìn)行過JavaWeb開發(fā)的同學(xué)都了解,在進(jìn)行后臺開發(fā)時不僅需要完成系統(tǒng)功能的開發(fā),為了保證系統(tǒng)的健壯性還要同步編寫對應(yīng)的單元測試類。基于Spring Boot開發(fā)的項(xiàng)目中的test包用于存放單元測試類,同時也提供了對應(yīng)的注解來進(jìn)行單元測試的編寫,本文結(jié)合Mock對Spring Boot中的單元測試進(jìn)行總結(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)項(xiàng)目中的一個程序類,每個單元測試方法對應(yīng)程序類中的一個方法,為保證所測試方法的正確性,至少需要設(shè)計四個以上的測試用例,包含:正確用例、錯誤用例和邊界用例。編寫的注釋事項(xiàng)如下:
- 測試類的位置位于項(xiàng)目test包下,包的層級結(jié)構(gòu)與項(xiàng)目相同;
- 測試類的命名規(guī)則通常為 xxxTest.java,其中xxx表示待測試類名;
- 測試類中方法命名規(guī)則為testXxx,其中Xxx表示待測試方法名 ;
- 測試方法上加上注解 @Test;
話不多說,咱們直接開干。
常用注解
當(dāng)下是注解盛行時代,我們先來了解一下相關(guān)的幾個注解。
注解 | 說明 |
---|---|
@RunWith |
更改測試運(yùn)行器 , 缺省值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ù)點(diǎn),現(xiàn)在已經(jīng)鋪墊完成,下面,我們使用代碼來實(shí)現(xiàn)。
代碼實(shí)現(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ò)請求進(jìn)行測試的情形多見于應(yīng)用的Controller層。Spring測試框架提供MockMvc對象,可以在不需要客戶端-服務(wù)端請求的情況下進(jìn)行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ù)進(jìn)行回滾。
- @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("123@test.com");
- 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)造出一個模擬/虛假的對象,使我們的測試能順利進(jìn)行下去。
Mockito 是當(dāng)前最流行的 單元測試 Mock 框架。采用 Mock 框架,我們可以 虛擬 出一個 外部依賴,降低測試 組件 之間的 耦合度,只注重代碼的 流程與結(jié)果,真正地實(shí)現(xiàn)測試目的。
由于web服務(wù)或數(shù)據(jù)庫不可達(dá)時,可以對其進(jìn)行Mock,在測試時不需要真實(shí)的模塊也可完成測試。
常用的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層的單元測試進(jìn)行模擬后,得到的新的單元測試類如下 :
- @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)之處,敬請諒解指出。
本文轉(zhuǎn)載自微信公眾號「Java后端技術(shù)全?!?,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系Java后端技術(shù)全棧公眾號。