在一些企業的實踐中,要求開發人員編寫測試編碼來測試業務邏輯,以提高編碼的質量、降低錯誤的發生概率以及進行性能測試等。這些IDE在創建Spring Boot應用的時候已經引入了測試包,只需要看到pom.xml就可以看到的內容:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
spring-boot-starter-test 會引入JUnit的測試包,這也是現實中使用得最多的方案,所以下面基於它進行討論 。在Spring Boot可以支持多種方面的測試 , 如 JPA、 MongoDB、 REST風格和Redis 等。基於實用原則,這裏主要講解測試業務層類、 REST風格和 Mock 測試。
1.構建測試類
在創建Spring Boot項目的時候,IDE會同時構建測試環境 ,這一步不需要自己處理。這裏看到IDE 自動創建的測試包(test),下面會包含一個可運行測試的文件,如下:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author TaoistQu
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestApplicationTests {
@Test
public void contextLoads() {
}
}
這裏的contextLoads是一個空的邏輯,其中註解@RunWith所載入的類SpringRunner是Spring結合JUnit的運行器,所以這裏可以進行JUnit測試。註解@SpringBootTest是可以配置Spring Boot的關於測試的相關功能。上述的contextLoads是一個空實現,下面舉例說明如何進行測試。下面假設已經開發好了UserService接口的Spring Bean,並且這個接口提供了getUser方法來獲取用戶信息。基於這個假設,測試代碼如代碼如下所示:
import cn.test.pojo.User;
import cn.test.service.UserService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author TaoistQu
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestApplicationTests {
//注入用戶服務類
@Autowired
private UserService userService;
@Test
public void contextLoads() {
User user = userService.getUser(2);
Assert.assertNotNull(user);
System.out.println(user);
}
}
代碼中UserService可以直接從IoC容器中注入,無須再進行任何處理。而在方法中使用了斷言來判斷用戶是否爲空,這便是最爲主要的測試方式。但並不是所有的方法都能很好地進行測試,例如之前談到的RestTemplate調用其他的微服務得到的數據,可能在進行測試之時,該微服務因爲特殊原因沒有開發完成,無法爲當前項目提供測試數據,導致正常的測試無法進行。這時Spring還會給予更多的支持,作爲輔助去消除這些因素給測試帶來的影響。
2.使用隨機端口和REST風格測試
有時候,本機已經啓動了8080端口,這時進行測試會就會佔用這個端口。爲了克服這個問題,在Spring Boot中提供了隨機端口的機制。這裏假設一個基於REST風格的請求
GET /user/get_user/{id}
已經開發好了,而且本地已經開啓8080端口服務。這裏就可以使用隨機端口進行測試了,如下代碼所示:
import cn.tpson.test.pojo.User;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author TaoistQu
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class TestApplicationTests {
@Autowired
TestRestTemplate restTemplate;
@Test
public void getUser() {
User user = restTemplate.getForObject("/user/get_user/{id}", User.class, 1);
Assert.assertNotNull(user);
System.out.println(user);
}
}
首先這裏配置了註解@SpringBootTest的配置項webEnvironment爲隨機端口啓動,這樣在運行測試的時候就會使用隨機端口啓動。其次注入了REST測試模板(TestRestTemplate),它是由Spring Boot的機制自動生成的,它的使用方法在第11章中已經闡述,所以這裏就不再贅述它的使用方法。在testGetUser方法中,標註了@Test,說明它是JUnit的測試方法之一,其邏輯是測試REST風格的請求(獲取用戶)。通過這些就能夠對控制器的邏輯也進行測試。
3.Mock測試
假設當前服務主要是提供用戶方面的功能,有時候還希望查看用戶購買了哪些產品以及產品的詳情,而產品的詳情是產品微服務提供的。這時,當前服務就希望通過一個產品服務接口(ProductService),基於REST風格調用產品微服務來獲取產品的信息。然而當前的產品微服務還未能提供相關的功能,因此當前的測試不能繼續進行。這時,Mock測試的理念就到來了。Mock測試是在測試過程中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法。簡單地說,如果產品服務接口(ProductService)的getProduct方法當前無法調度產品微服務,那麼Mock測試就可以給一個虛擬的產品,讓當前測試能夠繼續。下面舉例說明。這裏假設需要獲取一個產品的信息,然後產品微服務還沒有能提供相關的功能。這時希望能夠構建一個虛擬的產品結果來讓其他的測試流程能夠繼續下去。下面用如代碼清單16-9所示的代碼來模擬這個場景。
import cn.test.pojo.Product;
import cn.test.service.ProductService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author TaoistQu
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class TestApplicationTests {
@MockBean
private ProductService mockService;
@Test
public void testMock() {
Product mockProduct = new Product();
mockProduct.setId(1);
mockProduct.setName("TaoistQu");
BDDMockito.given(mockService.getProduct(1)).willReturn(mockProduct);
Product product = mockService.getProduct(1);
System.out.println(product);
Assert.assertTrue(product.getId() == 1);
}
}
代碼中註解@MockBean代表對哪個Spring Bean使用Mock測試。所以在測試方法testGetProduct中,先是構建虛擬對象,因爲按假設ProductService並不能提供服務,所以就只能先模擬產品(mockProduct)。模擬對象後,使用Spring Boot引入的Mockito來指定Mock Bean、方法和參數,並指定返回的虛擬對象。這樣當進行測試產品服務(ProductService)時,在調用getProduct方法且參數爲1的情況下,它就會返回之前構建的虛擬對象。調試結果:
參考書目:
- 《SpringBoot In Action》-【美】 Craig Walls
- 《深入淺出Spring Boot 2.x》–楊開振