Spring Boot 2+Thymeleaf企业应用实战
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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服务。