一、概述
这一章讲解如何使用 Spring 来整合测试框架,并且讲解单元测试在 Spring 下的最佳实践,Spring 团队非常注重测试驱动开发,Spring IoC 也可以使得测试变得非常简单。
二、集成测试
集成测试最重要的一点是开发人员不需要将应用部署到服务器或者连接其他服务,就能将功能代码进行测试。
Spring 框架提供了spring-test
模块,在org.springframework.test
包下提供了在 Spring 容器中集成测试的类,测试不依赖任何服务器和其他部署环境,所以它比简单的单元测试慢,但比 Selenium 测试和部署到远程服务器上测试要快的多。
1. 常用注解
Spring 整合测试框架,提供了下面常用注解:
1.1@BootstrapWith
类级别注解,配置 Spring TestContext 框架如何引导启动,一般不用配置,使用默认的就行。
1.2@ContextConfiguration
类级别注解,集成测试中加载或配置ApplicationContext
,使用locations
参数指定基于XML配置的文件路径,使用classes
参数指定基于注解配置的配置类。
@ContextConfiguration("/test-config.xml") public class XmlApplicationContextTests { // class body... } @ContextConfiguration(classes = TestConfig.class) public class ConfigClassApplicationContextTests { // class body... }
1.3@WebAppConfiguration
类级别注解,集成测试中加载或配置WebApplicationContext
,默认从file:src/main/webapp
作为根路径加载 Web 应用资源。
1.4@ActiveProfiles
类级别注解,在集成测试中,激活 Spring 中定义 Profile 来加载ApplicationContext
,Profile 详细用法参考文章Spring深入浅出IoC(下)。
@ContextConfiguration @ActiveProfiles({"dev", "integration"}) public class DeveloperIntegrationTests { // class body... }
1.5@TestPropertySource
类级别注解,在集成测试中,指定 Spring 下的配置文件或配置属性。@TestPropertySource
加载的配置属性优先级最高,比操作系统的环境变量,和使用@PropertySource
注解声明的配置等优先级都要高,因此,如果这几种配置方式中存在相同的属性配置,则运行时会以优先级高的配置为准。
它支持指定 classpath 路径下的配置文件,或直接指定配置属性:
@ContextConfiguration @TestPropertySource("/test.properties") public class MyIntegrationTests { // class body... } @ContextConfiguration @TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) public class MyIntegrationTests { // class body... }
3. 单元测试
我们后面演示 Spring 项目应用中,如何集成单元测试,使用最高效简单的操作来演示基本的单元测试操作。
第一步:模拟一个 Spring 应用程序。主要是配置一个ApplicationContext
上下文,因为我们知道,项目中所有 Bean 的配置入口都是在ApplicationContext
容器里面进行操作的。
package cn.codeartis.spring; @Configuration @ComponentScan public class TestApplicationContext { }
这里我们使用一个简单的基于注解配置的配置类,在该类同级目录下编写业务代码如下:
// 业务接口 package cn.codeartist.spring.service; public interface DemoService { void service(); void service(String str); } // 业务实现类 @Service public class DemoServiceImpl implements DemoService { @Override public void service() { System.out.println("DemoServiceImpl.service"); } @Override public void service(String str) { System.out.println("DemoServiceImpl.service::" + str); } }
第二步:编写单元测试代码。如果我们在 Spring 项目中使用的集成测试框架是一样的,可以写一个抽象类为父类来作为配置全局测试框架的类。
package cn.codeartis.spring.test; @RunWith(SpringRunner.class) @ContextConfiguration(classes = TestApplicationContext.class) public abstract class AbstractSpringRunnerTests { }
这里我们指定了一个 Spring 的Runner
和要测试的ApplicationContext
上下文,对于熟悉 JUnit 测试工具的人来说对于Runner
并不陌生,在该类同级目录下编写测试代码如下:
package cn.codeartis.spring.test.service; public class DemoServiceTest extends AbstractSpringRunnerTests { @Autowired private DemoService demoService; @Test public void service() { demoService.service(); } }
此时一个简单的单元测试示例就完成了。
4. Mock功能
Spring 自带集成了Mockito工具,我们可以使用它来简单地实现业务代码的 Mock 功能,因为在业务中有调用第三方接口,或者使用微服务,而只想测试自己的业务代码,不管其他业务代码,就可以使用 Mock 来解决这个问题。假如我们对前面的测试代码进行 Mock,代码如下:
public class MockServiceTest extends AbstractSpringRunnerTests { @MockBean private DemoService demoService; @Before public void mock() { BDDMockito.doAnswer(invocation -> { System.out.println("Mock service."); return null; }).when(demoService).service(); } @Test public void service() { demoService.service(); } }
Mockito 的功能很强大,还支持其他 Mock 场景,这里我们不详细讲,后面我们会有单独的文章来介绍。
5. JUnit特性
Spring Test 也支持 JUnit 的一些特性,比如:@Repeat
、@Timed
等注解的使用,还有其他测试方式,但这里我们要注意,前面我们在 Spring 单元测试中使用的Runner
是SpringRunner
,当我们要在 Spring 中使用其他Runner
时,需要通过JUnit
中的Rule
来实现,比如要在 Spring 中进行参数测试:
@RunWith(Parameterized.class) public class DemoParameterizedTest extends AbstractSpringRunnerTests { @ClassRule public static final SpringClassRule springClassRule = new SpringClassRule(); @Rule public final SpringMethodRule springMethodRule = new SpringMethodRule(); @Parameterized.Parameter public String param; @Autowired private DemoService demoService; @Parameterized.Parameters(name = "{index}: {0}") public static String[] data() { return new String[]{"param1", "param2", "param3"}; } @Test public void service() { demoService.service(param); } }
此时需要在测试类中声明 Spring 提供的SpringClassRule
和SpringMethodRule
来集成 Spring 环境。