领先一步
VMware 提供培训和认证,助你加速进步。
了解更多在 Pivotal 工作的一大好处是他们有一个出色的敏捷开发部门,称为 Pivotal Labs。Labs 内部的团队是精益和 XP 软件方法(如结对编程和测试驱动开发)的积极倡导者。他们对测试的热爱对 Spring Boot 1.4 产生了特别的影响,因为我们开始收到关于可以改进的地方的出色反馈。这篇博文重点介绍了最新 M2 版本中刚刚出现的一些新的测试功能。
对任何 Spring @Component
进行单元测试的最简单方法是完全不涉及 Spring!最好始终尝试组织代码,以便可以直接实例化和测试类。这通常归结为几件事
使用 Spring Framework 4.3,编写使用构造函数注入的组件非常容易,因为你不再需要使用 @Autowired
。只要你有一个构造函数,Spring 就会隐式地将其视为自动装配目标
@Component
public class MyComponent {
private final SomeService service;
public MyComponent(SomeService service) {
this.service = service;
}
}
现在测试 MyComponent
就像直接创建它并调用一些方法一样简单
@Test
public void testSomeMethod() {
SomeService service = mock(SomeService.class);
MyComponent component = new MyComponent(service);
// setup mock and class component methods
}
当然,通常你需要更进一步,开始编写确实涉及 Spring 的集成测试。幸运的是,Spring Framework 有 spring-test
模块来提供帮助,不幸的是,在 Spring Boot 1.3 中有很多不同的使用方法。
你可能正在将 @ContextConfiguration
注解与 SpringApplicationContextLoader
结合使用
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=MyApp.class, loader=SpringApplicationContextLoader.class)
public class MyTest {
// ...
}
你可能选择了 @SpringApplicationConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(MyApp.class)
public class MyTest {
// ...
}
或者你可能将其中任何一个与 @IntegrationTest
结合使用
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(MyApp.class)
@IntegrationTest
public class MyTest {
// ...
}
或者与 @WebIntegrationTest
(或可能 @IntegrationTest
+ @WebAppConfiguration
) 结合使用
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(MyApp.class)
@WebIntegrationTest
public class MyTest {
// ...
}
你还可以混合使用在随机端口上运行服务器 (@WebIntegrationTest(randomPort=true)
) 和添加属性(使用 @IntegrationTest("myprop=myvalue")
或 @TestPropertySource(properties="myprop=myvalue")
)
选择很多!
使用 Spring Boot 1.4,事情应该变得更简单。有一个单独的 @SpringBootTest
注解用于常规测试,以及一些专门用于测试应用程序“切片”的变体(稍后会详细介绍)。
一个典型的 Spring Boot 1.4 集成测试看起来像这样
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class MyTest {
// ...
}
以下是正在发生的事情的细分
@RunWith(SpringRunner.class)
告诉 JUnit 使用 Spring 的测试支持运行。SpringRunner
是 SpringJUnit4ClassRunner
的新名称,它只是看起来更简洁一些。@SpringBootTest
的意思是“使用 Spring Boot 的支持进行引导”(例如,加载 application.properties
并提供所有 Spring Boot 的便利功能)webEnvironment
属性允许为测试配置特定的“Web 环境”。你可以在 MOCK
Servlet 环境中或在运行在 RANDOM_PORT
或 DEFINED_PORT
上的真实 HTTP 服务器中启动测试。@SpringBootTest
的 classes
属性。在这个例子中,我们省略了 classes
,这意味着测试将首先尝试从任何内部类中加载 @Configuration
,如果失败,它将搜索你的主 @SpringBootApplication
类。@SpringBootTest
注解还有一个 properties
属性,可用于指定应在 Environment
中定义的任何附加属性。属性现在加载的方式与 Spring 常规的 @TestPropertySource
注解完全相同。
这是一个更具体的例子,它实际调用了一个真实的 REST 端点
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class MyTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void test() {
this.restTemplate.getForEntity(
"/{username}/vehicle", String.class, "Phil");
}
}
请注意,只要使用 @SpringBootTest
,TestRestTemplate
现在就可以作为 bean 使用。它预先配置为将相对路径解析为 http://localhost:${local.server.port}
。我们也可以使用 @LocalServerPort
注解将服务器实际运行的端口注入到测试字段中。
当你开始测试真实系统时,你常常会发现模拟出特定的 bean 会很有帮助。常见的模拟场景包括模拟在运行测试时无法使用的服务,或者测试在真实系统中难以触发的失败场景。
使用 Spring Boot 1.4,你可以轻松创建 Mockito mock,它可以替换现有的 bean,或者创建一个新的 bean
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SampleTestApplicationWebIntegrationTests {
@Autowired
private TestRestTemplate restTemplate;
@MockBean
private VehicleDetailsService vehicleDetailsService;
@Before
public void setup() {
given(this.vehicleDetailsService.
getVehicleDetails("123")
).willReturn(
new VehicleDetails("Honda", "Civic"));
}
@Test
public void test() {
this.restTemplate.getForEntity("/{username}/vehicle",
String.class, "sframework");
}
}
在这个例子中我们正在
VehicleDetailsService
创建一个 Mockito mock。ApplicationContext
作为 bean。setup
方法中 stub 行为。Mock 将在测试之间自动重置。它们也构成 Spring Test 使用的缓存键的一部分(因此无需添加 @DirtiesContext
)
Spies 的工作方式类似。只需用 @SpyBean
注解测试字段,即可让 spy 包裹 ApplicationContext
中任何现有的 bean。
如果你使用 spring-boot-starter-test
POM 导入测试依赖,从 1.4 开始你将获得出色的 AssertJ 库。AssertJ 提供了一个流畅的断言 API,它取代了 JUnit 有点基础的 org.junit.Assert
类。如果你之前没有见过它,一个基本的 AssertJ 调用看起来像这样
assertThat(library.getName()).startsWith("Spring").endsWith("Boot");
Spring Boot 1.4 提供了扩展的断言,你可以使用它们来检查 JSON 的编组和解组。Jackson 和 Gson 都提供了 JSON tester。
public class VehicleDetailsJsonTests {
private JacksonTester<VehicleDetails> json;
@Before
public void setup() {
ObjectMapper objectMapper = new ObjectMapper();
// Possibly configure the mapper
JacksonTester.initFields(this, objectMapper);
}
@Test
public void serializeJson() {
VehicleDetails details =
new VehicleDetails("Honda", "Civic");
assertThat(this.json.write(details))
.isEqualToJson("vehicledetails.json");
assertThat(this.json.write(details))
.hasJsonPathStringValue("@.make");
assertThat(this.json.write(details))
.extractingJsonPathStringValue("@.make")
.isEqualTo("Honda");
}
@Test
public void deserializeJson() {
String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}";
assertThat(this.json.parse(content))
.isEqualTo(new VehicleDetails("Ford", "Focus"));
assertThat(this.json.parseObject(content).getMake())
.isEqualTo("Ford");
}
}
JSON 比较实际上是使用 JSONassert 执行的,因此只需匹配 JSON 的逻辑结构。你还可以在上面的示例中看到如何使用 JsonPath 表达式来测试或提取数据。
Spring Boot 的自动配置功能非常适合配置应用程序运行所需的一切。不幸的是,全面的自动配置有时对于测试来说可能有点大材小用。有时你只是想配置应用程序的“切片”——Jackson 配置是否正确?我的 MVC 控制器是否返回正确的状态码?我的 JPA 查询是否会运行?
使用 Spring Boot 1.4,这些常见场景现在很容易测试。我们还使得构建自己的注解变得更容易,这些注解只应用你需要的自动配置类。
要测试应用程序的 JPA 切片(Hibernate + Spring Data),可以使用 @DataJpaTest
注解。@DataJpaTest
将会
@EntityScan
。一个典型的测试看起来像这样
@RunWith(SpringRunner.class)
@DataJpaTest
public class UserRepositoryTests {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository repository;
@Test
public void findByUsernameShouldReturnUser() {
this.entityManager.persist(new User("sboot", "123"));
User user = this.repository.findByUsername("sboot");
assertThat(user.getUsername()).isEqualTo("sboot");
assertThat(user.getVin()).isEqualTo("123");
}
}
上述测试中的 TestEntityManager
由 Spring Boot 提供。它是标准 JPA EntityManager
的替代品,提供了编写测试时常用的方法。
要测试应用程序的 Spring MVC 切片,可以使用 @WebMvcTest
注解。这将
@Controller
, @RestController
, @JsonComponent
等)这是一个测试单个控制器的典型示例
@RunWith(SpringRunner.class)
@WebMvcTest(UserVehicleController.class)
public class UserVehicleControllerTests {
@Autowired
private MockMvc mvc;
@MockBean
private UserVehicleService userVehicleService;
@Test
public void getVehicleShouldReturnMakeAndModel() {
given(this.userVehicleService.getVehicleDetails("sboot"))
.willReturn(new VehicleDetails("Honda", "Civic"));
this.mvc.perform(get("/sboot/vehicle")
.accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string("Honda Civic"));
}
}
如果你偏好 HtmlUnit,也可以使用 WebClient
而不是 MockMvc
。如果 selenium 更符合你的需求,可以切换到 WebDriver
。
如果你需要测试 JSON 序列化是否按预期工作,可以使用 @JsonTest
。这将
Module
或 @JsonComponent
beanJacksonTester
或 GsonTester
字段的初始化这是一个示例
@RunWith(SpringRunner.class)
@JsonTest
public class VehicleDetailsJsonTests {
private JacksonTester<VehicleDetails> json;
@Test
public void serializeJson() {
VehicleDetails details = new VehicleDetails(
"Honda", "Civic");
assertThat(this.json.write(details))
.extractingJsonPathStringValue("@.make")
.isEqualTo("Honda");
}
}
如果你想尝试 Spring Boot 1.4 中的新测试功能,可以从 http://repo.spring.io/snapshot/ 获取 M2 版本。GitHub 上也提供了 示例项目,以及 更新的文档。如果你对我们应该支持的附加“切片”有任何建议,或者希望看到任何进一步的改进,请提出问题。