針對不同的代碼層次, 有不同的測試注意事項.
我的理解是:
針對Mapper層或者是Dao層,由於需要跟數據庫交互, 寫完代碼的時候手工執行就好, 由於數據庫的數據的不穩定性,不適合用作大量回歸測試.
針對Service層的單元測試才是我們通常理解的那種單元測試或者說白盒測試.
針對Controller層的測試,如果Mock了Service層的行為,那這個算是白盒測試,如果沒有Mock Service層行為,而是直接調用,我覺得其實已經可以算是集成測試了,這個偏向黑盒測試的范疇,就是我們通常說的API測試.
1. 針對Mapper層或者Dao層
如果是有依賴其他Bean對象,可以先寫一個專門用來裝配Bean的類,並用注解TestConfiguration標注.
當然,這一步也不是必須的,這個只是為了方便在單元測試類中使用@Autowired引入對象.可以在單元測試類中,使用Product product = new Product()代替.
1 import org.springframework.boot.test.context.TestConfiguration; 2 import org.springframework.context.annotation.Bean; 3 4 import com.windy.mall.product.bean.Product; 5 6 @TestConfiguration 7 public class TestBeanConfiguration { 8 9 @Bean 10 public Product createProduct(){ 11 return new Product(); 12 } 13 14 }
單元測試類中必須要聲明:
(1) @RunWith(Spring.class),表示要在Spring環境中做測試, 於是就可以使用@Autowired等注解了,
(2) @SpringBootTest(classes=TestBeanConfiguration.class),表示可以使用SpringBoot環境了,這樣可以用@Autowired注解,把@Mapper注釋過的Mapper對象引入.為了引入外部依賴的Bean對象,需要指定classes=TestBeanConfiguration.class.
注意: 之所以要指定classes=TestBeanConfiguration.class,這時因為Product這個類,並沒有使用@Component等注解注入IOC容器.
可選:
(3) @Transactional注解來開啟事務
(4) @Rollback(true)表示開啟回滾數據功能,默認為True.
1 import org.junit.Assert; 2 import org.junit.Before; 3 import org.junit.Test; 4 import org.junit.runner.RunWith; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.boot.test.context.SpringBootTest; 7 import org.springframework.test.annotation.Rollback; 8 import org.springframework.test.context.junit4.SpringRunner; 9 import org.springframework.transaction.annotation.Transactional; 10 11 import com.windy.mall.product.bean.Product; 12 import com.windy.mall.product.bean.TestBeanConfiguration; 13 14 @RunWith(SpringRunner.class) 15 @SpringBootTest(classes=TestBeanConfiguration.class) 16 @Transactional 17 @Rollback(true) 18 public class ProductMapperTest { 19 20 @Autowired 21 private ProductMapper mapper; 22 23 @Autowired 24 private Product product; 25 26 @Before 27 public void setUp(){ 28 product.setPname("持續交付"); 29 product.setType("書籍"); 30 product.setPrice(69d); 31 } 32 33 @Test 34 public void testAdd() { 35 Assert.assertEquals(Integer.valueOf(1), mapper.add(product)); 36 } 37 38 }
2. 針對Service層
由於Service層需要調用Mapper層或者是Dao層,通常需要Mock Mapper對象.這里需要使用到@MockBean注解,Mock Mapper層的行為.
1 package com.windy.mall.product.service; 2 3 import org.junit.Assert; 4 import org.junit.Before; 5 import org.junit.Test; 6 import org.junit.runner.RunWith; 7 import org.mockito.BDDMockito; 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.boot.test.context.SpringBootTest; 10 import org.springframework.boot.test.mock.mockito.MockBean; 11 import org.springframework.test.context.junit4.SpringRunner; 12 13 import com.windy.mall.product.bean.Product; 14 import com.windy.mall.product.bean.TestBeanConfiguration; 15 import com.windy.mall.product.mapper.ProductMapper; 16 17 @RunWith(SpringRunner.class) 18 @SpringBootTest(classes = TestBeanConfiguration.class) 19 public class ProductServiceTest { 20 21 @MockBean 22 private ProductMapper mapper; 23 24 @Autowired 25 private Product product; 26 27 @Autowired 28 private ProductService service; 29 30 @Before 31 public void setUp() { 32 BDDMockito.given(mapper.add(product)).willReturn(1); 33 BDDMockito.given(mapper.add(null)).willReturn(0); 34 } 35 36 @Test 37 public void testAddService() { 38 Assert.assertEquals(Integer.valueOf(1), service.add(product)); 39 Assert.assertEquals(Integer.valueOf(0), service.add(null)); 40 } 41 42 }
3.針對Controller
有兩種方法可以做單元測試:
(1) 這里需要用到web環境(webEnvironment=WebEnvironment.RANDOM_PORT).需要用到TestRestTemplate這個對象作為客戶方,調用Controller(API)
1 import org.junit.Assert; 2 import org.junit.Test; 3 import org.junit.runner.RunWith; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.boot.test.context.SpringBootTest; 6 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 7 import org.springframework.boot.test.web.client.TestRestTemplate; 8 import org.springframework.test.context.junit4.SpringRunner; 9 10 @RunWith(SpringRunner.class) 11 @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) 12 public class ProductControllerTest { 13 14 @Autowired 15 private TestRestTemplate template; 16 17 @Test 18 public void test() { 19 String body = template.getForObject("/ms/product/1", String.class); 20 Assert.assertEquals("success", body); 21 } 22 23 }
(2) 如果是使用MockMVC對象來測試,需要開啟@AutoConfigureMockMvc注解
1 import org.junit.Test; 2 import org.junit.runner.RunWith; 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 5 import org.springframework.boot.test.context.SpringBootTest; 6 import org.springframework.test.context.junit4.SpringRunner; 7 import org.springframework.test.web.servlet.MockMvc; 8 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 9 import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 10 11 @RunWith(SpringRunner.class) 12 @SpringBootTest 13 @AutoConfigureMockMvc 14 public class ProductControllerTest2 { 15 16 @Autowired 17 private MockMvc mvc; 18 19 @Test 20 public void test() throws Exception { 21 mvc.perform(MockMvcRequestBuilders.get("/ms/product/1")) 22 .andExpect(MockMvcResultMatchers.status().isOk()) 23 .andExpect(MockMvcResultMatchers.content().string("success")); 24 } 25 26 }
