在一些企业的实践中,要求开发人员编写测试编码来测试业务逻辑,以提高编码的质量、降低错误的发生概率以及进行性能测试等。这些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》–杨开振