2.3 运行单元测试
单元测试对于程序来说非常重要,它不仅能增强程序的健壮性,而且也为程序的重构提供了依据,目前很多开源项目的测试覆盖率高达90%以上,可见大家对单元测试的重视。根据前面章节可知,Spring Boot运行Web应用,只需要执行main方法即可,那么如何测试这个Web程序?如何测试Spring Boot中的组件呢?这一节,将简单介绍Spring Boot的单元测试。
2.3.1 测试Web服务
Spring Boot提供了@SpringBootTest注解,可以让我们在单元测试中测试Spring Boot的程序。先为我们的项目加入“spring-boot-starter-test”依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>
新建测试类,用于测试第一个Spring Boot程序的“/hello”服务,测试类请见代码清单2-4。
代码清单2-4:codes\02\first-boot\src\test\java\org\crazyit\boot\c2\RandomPortTest.java
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class RandomPortTest { @Autowired private TestRestTemplate restTemplate; @Test public void testHello() { // 测试hello方法 String result = restTemplate.getForObject("/hello", String.class); assertEquals("Hello World", result); } }
在代码清单2-4中,为测试类加入了@RunWith、@SpringBootTest注解,其中为SpringBootTest配置了webEnvironment属性,表示在运行测试时,会为Web容器随机分配端口。在测试方法中,使用@Test注解修饰,使用TestRestTemplate调用“/hello”服务。
这个TestRestTemplate对象,实际上是对RestTemplate进行了封装,可以让我们在测试环境更方便使用RestTemplate的功能,例如代码清单2-4,我们不需要知道Web容器的端口是多少,就可以直接进行测试。
在代码清单2-4中配置了随机端口,如果想使用固定的端口,则可以将webEnvironment配置为WebEnvironment.DEFINED_PORT。使用该属性,会读取项目配置文件(例如application.properties)中的端口(server.port属性)来启动Web容器,如果没有配置,则使用默认端口8080。
2.3.2 模拟Web测试
在设置@SpringBootTest的webEnvironment属性时,不管设置为RANDOM_PORT还是设置为DEFINED_PORT,在运行单元测试时,都会启动一个真实的Web容器。如果不想启动真实的Web容器,则可以将webEnvironment属性设置为WebEnvironment.MOCK,来启动一个模拟的Web容器,如代码清单2-5所示。
代码清单2-5:codes\02\first-boot\src\test\java\org\crazyit\boot\c2\MockEnvTest.java
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.MOCK) @AutoConfigureMockMvc public class MockEnvTest { @Autowired private MockMvc mvc; @Test public void testHello() throws Exception { ResultActions ra = mvc.perform(MockMvcRequestBuilders.get(new URI( "/hello"))); MvcResult result = ra.andReturn(); System.out.println(result.getResponse().getContentAsString()); } }
为测试类加入@AutoConfigureMockMvc注解,让其进行mock相关的自动配置。在测试方法中,使用Spring的MockMvc进行模拟测试,向“/hello”发送请求并得到回应。
注意:webEnvironment属性的默认值是WebEnvironment.MOCK,只所以在代码清单2-5中“多此一举”,是为了展示该配置。
2.3.3 测试业务组件
前面都是针对Web容器进行测试,如果不想测试Web容器,只是想测试容器中的bean,则可以只启动Spring的容器,请见代码清单2-6。
代码清单2-6:codes\02\first-boot\src\main\java\org\crazyit\boot\c2\MyService.java codes\02\first-boot\src\test\java\org\crazyit\boot\c2\MyServiceTest.java
@Service public class MyService { public String helloService() { return "hello"; } } @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.NONE) public class MyServiceTest { @Autowired private MyService myService; @Test public void testHello() { String result = myService.helloService(); System.out.println(result); } }
在代码清单2-6中,新建了一个MyService的服务类,MyServiceTest会对该类进行测试,直接在测试类中注入MyService的实例。注意,SpringBootTest的webEnvironment属性被设置为NONE,因而Web容器将不会被启动。
2.3.4 模拟业务组件
在实际应用中,我们的程序可能会操作数据库,也有可能调用第三方接口,为了不让这些外部的不稳定因素影响单元测试的运行结果,可以使用mock来模拟某些组件的返回结果,确保被测试组件代码的健壮性。代码清单2-7显示了两个业务组件。
代码清单2-7:codes\02\first-boot\src\main\java\org\crazyit\boot\c2\MainService.java codes\02\first-boot\src\main\java\org\crazyit\boot\c2\RemoteService.java
@Service public class RemoteService { public String call() { return "hello"; } } @Service public class MainService { @Autowired private RemoteService remoteService; public void mainService() { System.out.println("这是需要测试的业务方法"); String result = remoteService.call(); System.out.println("调用结果:" + result); } }
RemoteService的call方法在正常情况下会返回hello字符串,MainService中的mainService方法会调用call方法。假设call方法无法正常运行,为了能测试MainService,我们需要模拟call方法的返回结果。代码清单2-8为MainService的测试方法。
代码清单2-8:codes\02\first-boot\src\test\java\org\crazyit\boot\c2\MockTest.java
@RunWith(SpringRunner.class) @SpringBootTest public class MockTest { @MockBean private RemoteService remoteService; @Autowired private MainService mainService; @Test public void testMainService() { // 模拟remoteService的call方法返回angus BDDMockito.given(this.remoteService.call()).willReturn("angus"); mainService.mainService(); } }
在测试类中,使用MockBean来修饰需要模拟的组件,在测试方法中使用了Mockito的API来模拟remoteService的call方法返回。在模拟中这个方法被调用后,将会返回“angus”字符串,运行代码清单2-8,输出结果如下:
这是需要测试的业务方法 调用结果:angus
根据结果可知,RemoteService的call方法被成功模拟。
这一节,简单介绍了如何在Spring Boot中进行单元测试,本节的知识基本上能满足大部分的需求,由于篇幅所限,在此不展开讨论。我们下面介绍如何使用Spring Boot来发布和调用REST服务。