Spring 集成测试
数据库测试
数据库测试通常有两种做法
- 采用嵌入式内存数据
- 采用真实的数据库,事务回滚
测试配置
我们做测试的一个关键点就是不能随意修改代码,切记,不能为了测试的需要而修改代码。如果真的要修改,也许应该修改的是设计,而不仅仅是代码。
不能修改代码,但我们可以提供不同的配置。然测试连接到不同的数据库上。
@ExtendWith(SpringExtension.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@TestPropertySource("classpath:test.properties")
public class TodoItemRepositoryTest {
...
}
嵌入式内存数据库
在 Java 世界中,常见的嵌入式内存数据库有 H2、HSQLDB、Apache 的 Derby 等。我们配置一个测试的依赖就好,以 H2 为例,像下面这样。
testImplementation "com.h2database:h2:$h2Version"
然后提供一个配置
jdbc.driverClassName=org.h2.Driver
jdbc.url=jdbc:h2:mem:todo;DB_CLOSE_DELAY=-1
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.hbm2ddl.auto=create
如果运气好,测试可以顺利运行。
为什么会归结于运气呢?这不是嵌入式数据库的问题,是每个数据库SQL不一致的问题,真实情况下总有一部分 SQL 只能运行在特定的引擎上。
所以,实际项目嵌入式数据库测试用的不多。
事务回滚
采用标准的应用配置
spring.datasource.url=jdbc:mysql://localhost:3306/todo_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=todo
spring.datasource.password=geektime
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
采用这种做法,就不必担心 SQL 不兼容的问题了。
我们的事务回滚体现在 @DataJpaTest 上, 它把数据库回滚做成默认配置,所以我们什么都不用做。
@ExtendWith(SpringExtension.class)
@DataJpaTest
public class ExampleRepositoryTests {
@Autowired
private TestEntityManager entityManager;
@Test
public void should_work() throws Exception {
this.entityManager.persist(new User("sboot", "1234"));
...
}
}
如果你用的不是 JPA 而是其它的数据访问方式,Spring 也给我们提供了 @JdbcTest,只要有 DataSource, 它就可以很好地工作起来,这适用于绝大多数的测试情况。模拟数据可以直接使用 sql。如下
@JdbcTest
@Sql({"test-data.sql"})
class EmployeeDAOIntegrationTest {
@Autowired
private DataSource dataSource;
...
}
Web 接口测试
我们采用整体集成的方式对系统进行测试,关键点是 @SpringBootTest
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
public class TodoItemResourceTest {
...
}
集成测试分两种
- 所有代码都集成的测试
- 针对外部组件的测试
测试 web 接口也有类似单元测试的方式
@WebMvcTest(TodoItemResource.class)
public class TodoItemResourceTest {
...
}
我们指定了要测试的组件 TodoItemResource.这个测试里,它只会集成与 TodoItemResource 相关的部分。
如果把它视为单元测试,服务层后面的代码都是外部的,我们可以采用模拟对象把它控制在可控范围内,MockBean 就开始发挥作用了。
@WebMvcTest(TodoItemResource.class)
public class TodoItemResourceTest {
@MockBean
private TodoItemService service;
@Test
public void should_add_item() throws Exception {
when(service.addTodoItem(TodoParameter.of("foo"))).thenReturn(new TodoItem("foo"));
...
}
}
@MockBean 标记的模拟对象会参与到组件组装的过程,我们就可以设置他的行为。如果 web 层与服务处有复杂交互,这种做法就可以很好的处理。但是,不建议做的这么复杂。
当年 Spring 摆脱了大部分对于应用服务器的依赖,但是 Web 却是它一直没有摆脱的。所以,怎么更好地不依赖于 Web 服务器进行测试,就是摆在 Spring 面前的问题。答案是 Spring 提供了模拟的 Web 环境。
我们再来回顾一下
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
public class TodoItemResourceTest {
@Autowired
private MockMvc mockMvc;
...
@Test
public void should_add_item() throws Exception {
String todoItem = "{ " +
"\"content\": \"foo\"" +
"}";
mockMvc.perform(MockMvcRequestBuilders.post("/todo-items")
.contentType(MediaType.APPLICATION_JSON)
.content(todoItem))
.andExpect(status().isCreated());
assertThat(repository.findAll()).anyMatch(item -> item.getContent().equals("foo"));
}
}
关键是 @AutoConfigureMockMvc,它为我们配置好了 MockMvc,剩下的就是我们使用这个配置好的环境进行访问。
所谓的模拟环境就是它根本没有启动真正的 Web 服务器,而是直接调用了我们的代码,省略了请求走网络的过程。但是请求进入服务器后的主要处理都在(无论是各种 Filter 的处理,还是从请求体到请求对象的转换)。所以MockMvc 是 Spring 轻量级开发的一个重要的组成部分。
总结
Spring 集成测试
- 数据库
- 事务回滚
- @DataJpaTest
- @JdbcTest
- @Transactional
- 内存数据库
- 事务回滚
- Web 接口
- 集成测试 @SpingBootTest
- 一个单元集成测试 @WebMvcTest
- 模拟对象 @MockBean
采用轻量级的测试手段,保证代码的正确性。