原文链接:
测试
Spring团队明确提倡采用TDD的方式进行软件开发,因此在单元测试的最佳实践的同时也涵盖了spring对集成测试的支持。spring团队已经发现正确的使用IoC确实可以简化单元测试和集成测试(在IoC中类里面的setter方法和合理的构造器的存在,能够使得类很容易组装在一个测试用例中,而不用手动的将service注册(或者其他的类似操作)在一起。这一章仅仅沉浸在测试能够给你提供的方便上。
1.简介
测试是企业软件开发的一个完整的组成部分。这个章节聚焦于IoC的原则为单元测试提供的附加价值,和获得Spring框架为集成测试的支持的好处上。(对企业应用软件完整的测试治理机制远远超出于这个参照手册的范围。)
2.单元测试
相对于传统的J2EE开发方式,DI能够减少代码对容器的依赖。构成应用的POJOs应该在Junit或TestNG中具备可测试性,这种可测试性仅仅是通过使用new操作进行对象实例化,而不需要依赖于Spring或者其他的容器。你可以通过mock对象的方式,对代码进行独立的测试。如果您遵循Spring的架构建议,那么代码库的清晰分层和组件化将简化单元测试。例如,你可以通过打桩或者mock Dao接口的方式来测试你的service层,而不需要在运行单元测试的时候获取持久层的数据。
因为没有创建运行时环境,真正意义上的单元测试能够以相当快的方式运行。强调一下,作为你开发方法论的一部分,真正的单元测试能够有效的提升你的生产力。你可能不需要这个测试的章节来帮助你针对你的基于IoC的应用编写有效的测试用例。但是本章这种描述了在某些单元测试的场景,Spring框架提供了mock对象和测试支持类。
2.1 mock对象
Spring提供了一组专注于mock的包:
- Environment
- JNDI
- Servlet API
- Spring Web Reactive
2.1.1 Environment
org.springframework.mock.env
包提供了对Environment
and PropertySource
这两个抽象mock实现。MockEnvironment
and MockPropertySource
在针对特定环境的代码,开发脱离容器的测试用例的时候非常实用。
2.1.2 JNDI
org.springframework.mock.jndi
包含了JNDI SPI的实现,通过它你可以为Test suits和stand-alone应用构建一个简单的JNDI的环境 。例如,如果JDBC数据源实例绑定了J2EE容器相同的JNDI的名字,那么你可以在测试场景中,在没有修改的情况下,复用代码和配置。
2.1.3. Servlet API
org.springframework.mock.web
包包含了一个综合的Servlet API mock对象的集合,这可以用于测试web contexts, controllers, 和filters. 与通过 EasyMock 进行动态的mock对象或者通过MockObjects进行完整的Servlet API的mock对象相比,这些通过使用spring web mvc框架mock的对象通常更加方便。
从spring5开始,
org.springframework.mock.web
包中mock的对象是基于servlet 4.0的标准。
Spring MVC Test framework基于mock Servlet API objects 为Spring MVC提供了一个完成的测试框架。详情参见 Spring MVC Test.
2.1.4. Spring Web Reactive
未完待续......
2.2 单元测试支持类
Spring提供了一组来支持单元测试,它们分布在两个不同的目录中。
2.2.1 General Testing Utilities
未完待续......
2.2.2. Spring MVC Testing Utilities
3.集成测试
这个章节(占了大量的摄于篇幅)覆盖了Spring应用的集成测试。它包括以下的主题:
- 概述
- 集成测试的目标
- JDBC测试支持
- 注解
- Spring TestContext Framework
- Spring MVC Test Framework
- PetClinic Example
3.1 概述
未完待续......
3.2 集成测试的目标
Spring集成测试支持包括如下几个目标:
- 管理测试用例之间Spring IoC容器的缓存
- 提供测试目标实例的依赖注入
- 为集成测试提供事务管理
- 为开发者在编写集成测试用例是提供spring特性的基础类
以下的四个小结将阐述每一个目标,并且提供相应的实现细节和配置细节的链接。
3.2.1 管理测试用例之间Spring IoC容器的缓存
Test classes typically declare either an array of resource locations for XML or Groovy configuration metadata — often in the classpath — or an array of annotated classes that is used to configure the application. These locations or classes are the same as or similar to those specified in web.xml
or other configuration files for production deployments.
By default, once loaded, the configured ApplicationContext
is reused for each test. Thus, the setup cost is incurred only once per test suite, and subsequent test execution is much faster. In this context, the term “test suite” means all tests run in the same JVM — for example, all tests run from an Ant, Maven, or Gradle build for a given project or module. In the unlikely case that a test corrupts the application context and requires reloading (for example, by modifying a bean definition or the state of an application object) the TestContext framework can be configured to reload the configuration and rebuild the application context before executing the next test.
See Context Management and Context Caching with the TestContext framework.
Spring TestContext Framework包含spring ApplicationContext实例和WebApplicationContext实例的加载,同时还包括了上下文的缓存。对加载上下文的缓存支持是十分重要的,因为启动的时间可以变为issue级别,当然这不是因为覆盖整个spring本身,而是因为spring的IoC容器对对象的实例化是要消耗时间的。例如一个项目包含50到100个Hibernater映射文件可能需要消耗10到20秒来加载映射文件,并且需要注意的是,在运行每一个测试用了之前的代价导致导致减慢所有的测试用例的运行,这将降低开发人员的生产力。
3.2.2 提供测试目标实例的依赖注入
3.2.3 为集成测试提供事务管理
3.2.4 为开发者在编写集成测试用例是提供spring特性的基础类
3.3 JDBC测试支持
未完待续......
3.4 注解
未完待续......
3.5 Spring TestContext Framework
未完待续......
3.6 Spring MVC Test Framework
Spring MVC Test framework通过适用于JUnit、TestNG或者其他测试框架的流式api,为测试Spring MVC的代码提供了支持. 这是基于 spring-test
模块的 Servlet API mock objects ,因此不需要运行Servlet容器。它使用DispatcherServlet
提供完整的Spring MVC运行时行为,并且在standalone模式的基础上,通过TestContext framework 为加载实际的Spring 配置提供支持,standalone模式中controllers能够手动实例化并且每次只测试一个。
Spring MVC Test 也为测试 RestTemplate
的代码提供客户端的支持。客户端测试mock了server端的相应,并且不需要运行服务端。
Spring Boot 提供了一个完整的运行server的端到端集成测试的选项。如果你也有相同的诉求,请移步到 Spring Boot reference page。了解更多的关于out-of-container 和end-to-end integration 测试, 详见 Differences between Out-of-Container and End-to-End Integration Tests.
3.6.1. Server-Side Tests
通过JUnit或TestNG为Controller写一个简单的测试用例很容易 : 简单的实例化Controller、注入mock对象或者打桩的依赖、传递MockHttpServletRequest, MockHttpServletResponse调用方法等等。但是,写这样一个单元测试用例依然有很多内容没有测试,如:request mappings、data binding、type conversion、 validation等等。甚至@InitBinder, @ModelAttribute, and @ExceptionHandler也是在请求处理的生命周期中。
Spring MVC Test的目标是通过真实的DispatcherServlet来执行requests和生成responses,来为测试controller提供一个有效的方式。
这是一个基于JUnit Jupiter的使用Spring MVC Test的例子:
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class ExampleTests {
private MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
void getAccount() throws Exception {
this.mockMvc.perform(get("/accounts/1")
.accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.name").value("Lee"));
}
}
这个测试用例是基于TestContext framework对WebApplicationContext的支持,来加载同包中的xml配置文件的。当然,Java-based和Groovy-based形式的配置也是支持的,详情请见 sample tests
The jsonPath
syntax is supported through the Jayway JsonPath project. Many other options for verifying the result of the performed request are discussed later in this document.
这个MockMvc
实例演示了一个/accounts/1
的GET请求,并且校验了响应的状态是200, content type是application/json
,响应体有一个key=name
,value= Lee
的JSON属性。jsonPath
的语法是基于Jayway JsonPath project的标准。后续的文档将会描述很多其他的验证结果的选项。
Static Imports
前面的测试用例中fluent API需要一些Static Imports,例如 MockMvcRequestBuilders.、 MockMvcResultMatchers.和 MockMvcBuilders.*。 如果使用Eclipse或者基于Eclipse的 Spring Tool Suite,需要在Eclipse的preferences → Java → Editor → Content Assist → Favorites添加为 “favorite static members” 。这样能够在桥下这些api的首字母时,进行相应的辅助提示。 其他IDEs (如:IntelliJ) 可能不需要附加的配置.然后检查static members的代码的自动补全支持。
Setup Choices
有两种方式用于创建MockMvc实例,第一种是通过TestContext framework加载Spring MVC 的配置,这种方式加载Spring的配置并且将WebApplicationContext注入到测试用例中,从而构建MockMvc实例。示例如下:
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration("my-servlet-context.xml")
public class MyWebTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
// ...
}
第二种方式是在不加字啊spring配置的情况下,手动创建controller实例。基础默认配置自动创建,这相当于MVC JavaConfig 和MVC namespace。可以在一定程度上自义定配置。示例如下:
public class MyWebTests {
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
// ...
}
到底选择哪种方式呢?
webAppContextSetup的方式加载了真实的Spring MVC 配置,结果就是更加完整的集成测试。因为TestContext framework缓存了Spring 已经加载的配置,这有助于更加快速的测试,即使test suite中有很多的tests。
也可以通过Spring配置将mock Service 注入到 controller中,来保证聚焦在web层的测试。示例如下:
<!--声明mock service-->
<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.example.AccountService"/>
</bean>
//将mock servcie 注入到 测试用例中。
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-servlet-context.xml")
public class AccountTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Autowired
private AccountService accountService;
// ...
}
另一方面standaloneSetup的方式更接近于单元测试。这种方式每次只测试一个controller,并且可以手动注入mock依赖到controller中,而不涉及加载Spring配置。这种方式更专注于style,更容易看出正在被测试的controller,不管Sping MVC的配置等是否生效. standaloneSetup的模式对于ad-hoc(点对点)的测试和调试问题是一种非常方便的方式。
关于集成测试还是单元测试的讨论是没有对与错之分的,但是使用standaloneSetup 的方式暗示了补充webAppContextSetup的测试的必要性。总之,可以使用webAppContextSetup的方式,编写全部的测试用例,来保证总是对Spring MVC 进行测试。
Setup Features
Performing Requests
Defining Expectations
Filter Registrations
Differences Between Out-of-Container and End-to-End Integration Tests
Further Server-Side Test Examples
3.6.2. HtmlUnit Integration
3.6.3. Client-Side REST Tests
3.7 PetClinic Example
未完待续......