如何使用Java单元测试助手高效构建和改进Spring测试?
Spring框架(连同Spring Boot一起)是最流行的Java企业软件框架之一。它在关键任务应用中的使用意味着它的质量和安全性受到了严格的审查。
此前,我们讨论了 开发人员如何不喜欢单元测试,尽管它的改进记录已被证实 ,并详细介绍了Parasoft Jtest的单元测试助手如何提供一种指导性和自动化的测试方法,使测试不仅更容易接受,而且更容易和更有效。 在这篇文章中,我将继续以Spring框架为主题,向你展示如何在这个重要的应用框架中利用自动化和引导式测试。
测试Spring应用的挑战
Spring框架为集成测试提供了不错的支持,但要正确设置测试用例,需要大量的手工编码。构建和维护Spring应用的测试给开发人员带来了一系列独特的挑战,包括以下几点:
- 必须对Spring框架进行初始化和配置
- 应用程序通常有第三方库的依赖性(持久存储、外部服务等)
- 应用程序通常使用内置的Spring功能,用于会话、安全、消息传递等。对于刚接触Spring测试的开发人员来说,这些功能的设置可能会很棘手
- 需要适当配置应用程序的依赖关系(即Bean)
这些挑战,再加上编写全面的可维护的测试套件一般需要花费大量的时间,导致开发人员没有编写足够的测试,反过来,这又会导致安全漏洞、缺陷、回归和许多令人头痛的问题。
Parasoft Jtest Unit Test Assistant可以帮助你,它使生成、改进和维护JUnit测试的过程变得更加简单和省时,这样开发人员就可以快速构建好的测试,并回到他们更加偏好的事情上——编写代码。
Spring MVC测试框架
Spring框架包括一个测试框架,它使控制器、服务和其他组件的测试变得更加容易。它包括配置Spring测试容器、调用Controller处理方法和使用自定义断言验证行为的功能。
举一个Spring MVC控制器的例子:
@Controller
public class TodoController { @Autowired
private TodoService service;
@GetMapping("/") public String findAll(Model model) {
List<Todo> todos = service.findAll();
model.addAttribute("todos", todos);
return "todo/list";
}
这个示例中,控制器实现了一个简单的REST服务,以从 "待办事项 "列表中获取项目。它依赖于一个TodoService,其中包含业务逻辑。
为了测试findAll方法,我们需要一个JUnit测试,它可以完成以下工作:
- 在Spring容器中配置被测试的Controller和TodoController所依赖的TodoService。
- 向findAll处理方法发送一个有效的请求。
- 验证响应的元素,包括返回值("todo/list")和Model属性 "todos"。
再一个Spring MVC Junit测试的例子可能是这样的:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TodoControllerTest {
@Autowired
TodoController controller;
@Autowired
TodoService todoService;
MockMvc mockMvc;
@Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
@Configuration
static class Config {
@Bean
public TodoController getTodoController() {
return new TodoController();
@Bean
public TodoService getTodoService() {
return new TodoService();
@Test
public void testFindAll() throws Exception {
mockMvc.perform(get("/")).andExpect(view().name("todo/list"));
}
上面的例子是一个非常简单的测试——但还是有很多“样板”代码要写,有很多事情要做。在这个例子中,我们使用一个内部的Configuration类为Spring配置了一个控制器和它的服务,然后使用MockMvc函数向处理程序发送请求。然后,我们使用MockMvc函数向处理者方法发送请求(使用perform),并使用andExpect验证返回的视图名称。
上面的测试有什么问题吗?其实没什么——但想象一下一个更复杂的控制器方法,有多个处理方法,接受更多的参数并产生更多的输出。编写测试会花费更多的时间,特别是如果良好的测试覆盖率很重要的话。此外,大多数真实的测试需要更多的配置(XML或类配置、会话和环境、安全等)。
使用Parasoft Jtest生成Spring测试
Parasoft Jtest的单元测试助手可以帮助开发人员以多种方式编写Spring测试:
- 自动快速生成Spring MVC测试的模板代码
- 自动生成参数化测试,提高测试覆盖率
- 模拟依赖关系,以隔离辅助方法并简化测试
- 在运行时收集覆盖率数据并分析测试流程
- 为改进测试提供建议,并提供快速修补办法
自动生成测试
在Parasoft Jtest中生成Spring测试是很直接的——只需在你的IDE中为你的控制器选择一个Spring处理方法,并选择一个测试创建动作:
选择Regular Spring或Parameterized Spring会自动为你生成模板化的Spring MVC测试,包括Configuration类(以及你的控制器所依赖的所有Bean)。mockMvc.perform调用也被添加进来,并被预先配置为调用创建测试的处理方法。Jtest 单元测试助手甚至增加了一些例子断言,你可以取消注释和配置。
Parasoft Jtest支持使用XML或类配置生成测试,通过在偏好设置中设置 "ContextConfiguration attributes for Spring tests "选项。
模拟依赖性
在单元测试中管理依赖关系是至关重要的,因为大部分的复杂性和工作来自于隔离被测单元。Jtest单元测试助手默认使用Mockito或PowerMockito来模拟依赖关系(如果你不希望这样,可以在偏好设置中禁用)。模拟依赖关系允许测试控制这些依赖关系,将处理程序方法与应用程序的其他部分隔离开来,以便将测试工作集中在处理程序上。在我们的示例处理程序中,findAll方法是在TodoService上被调用的——如果我们使用一个真正的TodoService,我们就有效地测试了TodoController和TodoService。这可能是我们在集成测试中想要的,但不是在单元测试中想要的。在测试中模拟TodoService.findAll的响应,可以让我们把测试工作集中在处理方法上。
(如果你想了解更多关于在Spring测试中模拟依赖关系的信息,请阅读我的 下一篇文章 。)
Spring Boot
由于Spring Boot为Bean提供了简化的配置,以及额外的测试注释,所以当单元测试助理在你的项目中检测到Spring Boot时,会生成略有不同的测试。例如,MockMvc是自动连接的,依赖关系使用@MockBean进行模拟,并使用@SpringBootTest注解。
运行测试和分析结果
你可以使用任何普通的JUnit运行器运行生成的测试。Parasoft Jtest提供了运行JUnit和分析测试的工具栏操作。
测试运行后,会显示测试执行流程,单元测试助手会提出改进测试的建议,并在IDE中报告:
提供处理程序方法输入
处理程序方法通常被配置为接受路径、查询或其他参数作为方法的参数。要测试MVC处理程序方法,可以使用MockMvc来构建路径/查询和调用该方法所需的任何其他参数。
Jtest单元测试助理会自动配置mockMvc.perform调用来调用处理程序方法。单个参数在测试中显示为局部变量(或参数化测试中的参数),需要配置这些参数才能使测试正确运行。
例如(下文单元测试助手缩写为UTA):
@Test public void testGetPerson() throws Throwable { // When String id = ""; // UTA: Configure an appropriate parameter value since the tested method depends on it ResultActions actions = mockMvc.perform(get("/people/" + id));
这里,需要配置 "id"字符串——如果没有,那么使用的路径将是"/people/",Spring将不会把提供的路径匹配到合适的处理方法。
class="p1″>单元测试助手会寻找各种类型的处理程序方法参数,并通过以下方式自动为它们准备测试:
- HttpSession(增加一个setAttribute()调用的例子)
- 头部(增加一个header()调用)
- 请求主体(添加一个payload变量和content()调用)
- 认证(在setup方法中添加了一个实例,以及一个principal()调用)
运行一个不会导致处理程序方法被调用的测试,会产生类似下面的建议:
验证处理程序方法的输出
根据处理程序方法要提供给调用者的内容,它可能返回多种类型。大多数情况下,处理方法会返回一个ModelAndView(或类似的对象,如Model或RedirectView)来为页面提供服务,或者返回某种类型的ResponseEntity(有时只是要序列化的原始对象)。这个响应可以访问Spring MVC测试框架进行验证。
例如,Jtest单元测试助理为一个返回ModelAndView的处理方法添加了以下断言:
// When
String id = "1";
ResultActions actions = mockMvc.perform(get("/people/" + id));