小白搞 Spring Boot 單元測試
大家好,我是田維常,今天給大家分享來自于一位小伙的投稿。
內容是:Spring Boot 中的單元測
前言
何為單元測試
單元測試的目的: 測試當前所寫的代碼是否是正確的, 例如輸入一組數據, 會輸出期望的數據; 輸入錯誤數據, 會產生錯誤異常等. 在單元測試中, 我們需要保證被測系統是獨立的(SUT 沒有任何的 DOC), 即當被測系統通過測試時, 那么它在任何環境下都是能夠正常工作的. 編寫單元測試時, 僅僅需要關注單個類就可以了. 而不需要關注例如數據庫服務, Web 服務等組件。
背景
進行過JavaWeb開發的同學都了解,在進行后臺開發時不僅需要完成系統功能的開發,為了保證系統的健壯性還要同步編寫對應的單元測試類。基于Spring Boot開發的項目中的test包用于存放單元測試類,同時也提供了對應的注解來進行單元測試的編寫,本文結合Mock對Spring Boot中的單元測試進行總結。
環境: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>
單元測試的創建
每個單元測試類對應項目中的一個程序類,每個單元測試方法對應程序類中的一個方法,為保證所測試方法的正確性,至少需要設計四個以上的測試用例,包含:正確用例、錯誤用例和邊界用例。編寫的注釋事項如下:
- 測試類的位置位于項目test包下,包的層級結構與項目相同;
- 測試類的命名規則通常為 xxxTest.java,其中xxx表示待測試類名;
- 測試類中方法命名規則為testXxx,其中Xxx表示待測試方法名 ;
- 測試方法上加上注解 @Test;
話不多說,咱們直接開干。
常用注解
當下是注解盛行時代,我們先來了解一下相關的幾個注解。
注解 | 說明 |
---|---|
@RunWith |
更改測試運行器 , 缺省值org.junit.runner.Runner |
@Before |
初始化方法,執行當前測試類的每個測試方法前執行 |
@Test |
測試方法,在這里可以測試期望異常和超時時間 |
@Test(timeout = 10000) |
超時測試方法,若測試方法未在指定時間內結束則junit 自動將其標記為失敗 |
@Transactional |
聲明式事務管理,用于需數據庫事務管理的測試方法 |
@Rollback(true) |
數據庫回滾,避免測試數據污染數據庫 |
相關理論和技術點,現在已經鋪墊完成,下面,我們使用代碼來實現。
代碼實現
我們分別做三層的測試: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層,也可以稱之為網絡請求測試。對于網絡請求進行測試的情形多見于應用的Controller層。Spring測試框架提供MockMvc對象,可以在不需要客戶端-服務端請求的情況下進行Web測試.
測試開始之前需要建立測試環境,setup方法被@Before修飾。通過MockMvcBuilders工具,創建一個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層的方法直接操作數據庫,為避免測試數據對數據庫造成污染,使用注解@Transactional和@Rollback在測試完成后對測試數據進行回滾。
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class ScoreControllerTestNew {
- @Autowired
- private UserDao userDao;
- /**
- * 測試插入數據
- */
- @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);
- }
- }
到此,關于三個層面的測試就已經搞定了,下面我們來看看,如何使用Mockito模擬數據庫操作。
使用Mockito模擬數據庫操作
前面在介紹web請求測試時使用了Mock技術,該技術常用于被測試模塊(方法)依賴于外部系統(web服務、中間件或是數據庫)時。
Mock 的中文譯為仿制的,模擬的,虛假的。對于測試框架來說,即構造出一個模擬/虛假的對象,使我們的測試能順利進行下去。
Mockito 是當前最流行的 單元測試 Mock 框架。采用 Mock 框架,我們可以 虛擬 出一個 外部依賴,降低測試 組件 之間的 耦合度,只注重代碼的 流程與結果,真正地實現測試目的。
由于web服務或數據庫不可達時,可以對其進行Mock,在測試時不需要真實的模塊也可完成測試。
常用的Mockito方法如下:
方法 | 簡介 |
---|---|
Mockito.mock(classToMock) |
模擬對象 |
Mockito.when(methodCall).thenReturn(value) |
參數匹配 |
Mockito.doReturn(toBeReturned).when(mock).[method] |
參數匹配(直接執行不判斷) |
Mockito.when(methodCall).thenAnswer(answer)) |
預期回調接口生成期望值 |
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"));
- }
- }
關于mockito相關,請參考官網:https://site.mockito.org/
后記
本文重在用代碼案例講解單元測試,篇幅有限,先分享到這里,如有不當之處,敬請諒解指出。
本文轉載自微信公眾號「Java后端技術全棧」,可以通過以下二維碼關注。轉載本文請聯系Java后端技術全棧公眾號。