青年IT男

个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。微信公众号:青年IT男。

《spring-boot-in-action》 第4章 测试

《spring-boot-in-action》 第4章 测试

第4章 测试

本章内容

  • 集成测试
  • 在服务器里测试应用程序
  • Spring Boot的测试辅助工具

有人说,如果你不知道要去哪,走就是了。但在软件开发领域,如果你没有目标,那结果往往是开发出一个满是bug的应用程序,没人用得了。

在编写应用程序时,明确目标的最佳方法就是写测试,确定应用程序的行为是否符合预期。如果测试失败了,你就有活要干了。如果测试通过了,那你就成功了(至少在你觉得还有其他测试要写之前,是这样的)。

究竟是在编写业务代码之前还是之后写测试,这并不重要。重要的是,写测试不仅仅是为了验证代码的准确性,还要确认它符合预期。测试也是一道保障,确认应用程序在改进的同时不会破坏已有的东西。

在编写单元测试的时候,Spring通常不需要介入。Spring鼓励松耦合、接口驱动的设计,这些都能让你很轻松地编写单元测试。但是在写单元测试时并不需要用到Spring。

但是,集成测试要用到Spring。如果生产应用程序使用Spring来配置并组装组件,那么测试就需要用它来配置并组装那些组件。

Spring的SpringJUnit4ClassRunner可以在基于JUnit的应用程序测试里加载Spring应用程序上下文。在测试Spring Boot应用程序时,Spring Boot除了拥有Spring的集成测试支持,还开启了自动配置和Web服务器,并提供了不少实用的测试辅助工具。

在本章中,我们会看到Spring Boot的各种集成测试支持。让我们先来看看如何在Spring Boot应用程序上下文里做测试。

4.1 集成测试自动配置

Spring Framework多项工作的核心是将所有组件编织在一起,构成一个应用程序。整个过程就是读取配置说明(可以是XML、基于Java的配置、基于Groovy的配置或其他类型的配置),在应用程序上下文里初始化Bean,将Bean注入依赖它们的其他Bean中。

对Spring应用程序进行集成测试时,让Spring遵照生产环境来组装测试目标Bean是非常重要的一点。当然,你也可以手动初始化组件,并将它们注入其他组件,但对那些大型应用程序来说,这是项费时费力的工作。而且,Spring提供了额外的辅助功能,比如组件扫描、自动织入和声明性切面(缓存、事务和安全,等等)。你要把这些活都干了,基本也就是把Spring再造了一次,最好还是让Spring替你把重活都做了吧,哪怕是在集成测试里。

Spring自1.1.1版就向集成测试提供了极佳的支持。自Spring 2.5开始,集成测试支持的形式就变成了SpringJUnit4ClassRunner。这是一个JUnit类运行器,会为JUnit测试加载Spring应用程序上下文,并为测试类自动织入所需的Bean。

举例来说,看一下代码清单4-1,这是一个非常基本的Spring集成测试。

代码清单4-1 用SpringJUnit4ClassRunner对Spring应用程序进行集成测试

原书P77~78代码

文字

Loads application context:加载应用程序上下文

Injects address service:注入地址服务

Tests address service:测试地址服务

如你所见,AddressServiceTests上加注了@RunWith@ContextConfiguration注解。@RunWith的参数是SpringJUnit4ClassRunner.class,开启了Spring集成测试支持。{![在Spring 4.2里,你可以选择基于规则的SpringClassRuleSpringMethodRule来代替SpringJUnit4ClassRunner。]}与此同时,@ContextConfiguration指定了如何加载应用程序上下文。此处我们让它加载AddressBookConfiguration里配置的Spring应用程序上下文。

除了加载应用程序上下文,SpringJUnit4ClassRunner还能通过自动织入从应用程序上下文里向测试本身注入Bean。因为这是一个针对AddressService Bean的测试,所以需要将它注入测试。最后,testService()方法调用地址服务并验证了结果。

虽然@ContextConfiguration在加载Spring应用程序上下文的过程中做了很多事情,但它没能加载完整的Spring Boot。Spring Boot应用程序最终是由SpringApplication加载的。它可以显式加载(如代码清单2-1所示),在这里也可以使用SpringBootServletInitializer(我们会在第8章里看到具体做法)。SpringApplication不仅加载应用程序上下文,还会开启日志、加载外部属性(application.properties或application.yml),以及其他Spring Boot特性。用@ContextConfiguration则得不到这些特性。

要在集成测试里获得这些特性,可以把@ContextConfiguration替换为Spring Boot的@SpringApplicationConfiguration

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(
      classes=AddressBookConfiguration.class)
public class AddressServiceTests {
  ...
}

@SpringApplicationConfiguration的用法和@ContextConfiguration大致相同,但也有不同的地方,@SpringApplicationConfiguration加载Spring应用程序上下文的方式同SpringApplication相同,处理方式和生产应用程序中的情况相同。这包括加载外部属性和Spring Boot日志。

我们有充分的理由说,在大多数情况下,为Spring Boot应用程序编写测试时应该用@SpringApplicationConfiguration代替@ContextConfiguration。在本章中,我们当然也会用@SpringApplicationConfiguration来为Spring Boot应用程序(包括那些面向前端的应用程序)编写测试。

说到Web测试,这正是我们接下来要做的。

4.2 测试Web应用程序

Spring MVC有一个优点:它的编程模型是围绕POJO展开的,在POJO上添加注解,声明如何处理Web请求。这种编程模型不仅简单,还让你能像对待应用程序中的其他组件一样对待这些控制器。你还可以针对这些控制器编写测试,就像测试POJO一样。

举例来说,考虑ReadingListController里的addToReadingList()方法:

@RequestMapping(method=RequestMethod.POST)
public String addToReadingList(Book book) {
  book.setReader(reader);
  readingListRepository.save(book);
  return "redirect:/readingList";
}

如果忽略@RequestMapping注解,你得到的就是一个相当基础的Java方法。你立马就能想到这样一个测试,提供一个ReadingListRepository的模拟实现,直接调用addToReadingList(),判断返回值并验证对ReadingListRepositorysave()方法有过调用。

该测试的问题在于,它仅仅测试了方法本身,当然,这要比没有测试好一点。然而,它没有测试该方法处理/readingList的POST请求的情况,也没有测试表单域绑定到Book参数的情况。虽然你可以判断返回的String包含特定值,但没法明确测试请求在方法处理完之后是否真的会重定向到/readingList。

要恰当地测试一个Web应用程序,你需要投入一些实际的HTTP请求,确认它能正确地处理那些请求。幸运的是,Spring Boot开发者有两个可选的方案能实现这类测试。

  • Spring Mock MVC:能在一个近似真实的模拟Servlet容器里测试控制器,而不用实际启动应用服务器。
  • Web integration tests:在嵌入式Servlet容器(比如Tomcat或Jetty)里启动应用程序,在真正的应用服务器里执行测试。

这两种方法各有利弊。很明显,启动一个应用服务器会比模拟Servlet容器要慢一些,但毫无疑问基于服务器的测试会更接近真实环境,更接近部署到生产环境运行的情况。

接下来,你会看到如何使用Spring Mock MVC测试框架来测试Web应用程序。然后,在4.3节里你会看到如何为运行在应用服务器里的应用程序编写测试。

4.2.1 模拟Spring MVC

早在Spring 3.2,Spring Framework就有了一套非常实用的Web应用程序测试工具,能模拟Spring MVC,不需要真实的Servlet容器也能对控制器发送HTTP请求。Spring的Mock MVC框架模拟了Spring MVC的很多功能。它几乎和运行在Servlet容器里的应用程序一样,尽管实际并非如此。

要在测试里设置Mock MVC,可以使用MockMvcBuilders,该类提供了两个静态方法。

  • standaloneSetup():构建一个Mock MVC,提供一个或多个手工创建并配置的控制器。
  • webAppContextSetup():使用Spring应用程序上下文来构建Mock MVC,该上下文里可以包含一个或多个配置好的控制器。

两者的主要区别在于,standaloneSetup()希望你手工初始化并注入你要测试的控制器,而webAppContextSetup()则基于一个WebApplicationContext的实例,通常由Spring加载。前者同单元测试更加接近,你可能只想让它专注于单一控制器的测试,而后者让Spring加载控制器及其依赖,以便进行完整的集成测试。

我们要用的是webAppContextSetup()。Spring完成了ReadingListController的初始化,并从Spring Boot自动配置的应用程序上下文里将其注入,我们直接对其进行测试。

webAppContextSetup()接受一个WebApplicationContext参数。因此,我们需要为测试类加上@WebAppConfiguration注解,使用@AutowiredWebApplicationContext作为实例变量注入测试类。代码清单4-2演示了Mock MVC测试的执行入口。

代码清单4-2 为集成测试控制器创建Mock MVC

原文P80~81代码

文字

Enables web context testing 开启Web上下文测试

Injects WebApplicationContext:注入WebApplicationContext

Sets up MockMvc:设置MockMvc

@WebAppConfiguration注解声明,由SpringJUnit4ClassRunner创建的应用程序上下文应该是一个WebApplicationContext(相对于基本的非WebApplicationContext)。

setupMockMvc()方法上添加了JUnit的@Before注解,表明它应该在测试方法之前执行。它将WebApplicationContext注入webAppContextSetup()方法,然后调用build()产生了一个MockMvc实例,该实例赋给了一个实例变量,供测试方法使用。

现在我们有了一个MockMvc,已经可以开始写测试方法了。我们先写个简单的测试方法,向/readingList发送一个HTTP GET请求,判断模型和视图是否满足我们的期望。下面的homePage()测试方法就是我们所需要的:

@Test
public void homePage() throws Exception {
  mockMvc.perform(MockMvcRequestBuilders.get("/readingList"))
          .andExpect(MockMvcResultMatchers.status().isOk())
          .andExpect(MockMvcResultMatchers.view().name("readingList"))
          .andExpect(MockMvcResultMatchers.model().attributeExists("books"))
          .andExpect(MockMvcResultMatchers.model().attribute("books",
                   Matchers.is(Matchers.empty())));
}

如你所见,我们在这个测试方法里使用了很多静态方法,包括Spring的MockMvcRequestBuildersMockMvcResultMatchers里的静态方法,还有Hamcrest库的Matchers里的静态方法。在深入探讨这个测试方法前,先添加一些静态import,这样代码看起来更清爽一些:

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.
        ➥ MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.
        ➥ MockMvcResultMatchers.*;

有了这些静态import后,测试方法可以稍作调整:

@Test
public void homePage() throws Exception {
mockMvc.perform(get("/readingList"))
        .andExpect(status().isOk())
        .andExpect(view().name("readingList"))
        .andExpect(model().attributeExists("books"))
        .andExpect(model().attribute("books", is(empty())));
}

现在这个测试方法读起来就很自然了。首先向/readingList发起一个GET请求,接下来希望该请求处理成功(isOk()会判断HTTP 200响应码),并且视图的逻辑名称为readingList。测试还要断定模型包含一个名为books的属性,该属性是一个空集合。所有的断言都很直观。

值得一提的是,此处完全不需要将应用程序部署到Web服务器上,它是运行在模拟的Spring MVC中的,刚好能通过MockMvc实例处理我们给它的HTTP请求。

太酷了,不是吗?

让我们再来看一个测试方法,这次会更有趣,我们实际发送一个HTTP POST请求提交一本新书。我们应该期待POST请求处理后重定向回/readingList,模型将包含新添加的图书。代码清单4-3演示了如何通过Spring的Mock MVC来实现这个测试。

代码清单4-3 测试提交一本新书

原书P82~83代码

文字

Performs POST request:执行POST请求

Sets up expected book:配置期望的图书

Performs GET request:执行GET请求

很明显,代码清单4-3里的测试更加复杂,实际上是两个测试放在一个方法里。第一部分提交图书并检查了请求的结果,第二部分执行了一次对主页的GET请求,检查新建的图书是否在模型中。

在提交图书时,我们必须确保内容类型(通过MediaType.APPLICATION_FORM_URLENCODED)设置为application/x-www-form-urlencoded,这才是运行应用程序时浏览器会发送的内容类型。随后,要用MockMvcRequestBuildersparam方法设置表单域,模拟要提交的表单。一旦请求执行,我们要检查响应是否是一个到/readingList的重定向。

假定以上测试都通过,我们进入第二部分。首先设置一个Book对象,包含想要的值。我们用这个对象和首页获取的模型的值进行对比。

随后要对/readingList发起一个GET请求,大部分内容和我们之前测试主页时一样,只是之前模型中有一个空集合,而现在有一个集合项。这里要检查它的内容是否和我们创建的expectedBook一致。如此一来,我们的控制器看来保存了发送给它的图书,完成了工作。

至此,这些测试验证了一个未经保护的应用程序,和我们在第2章里写的应用程序很类似。但如果我们想要测试一个安全加固过的应用程序(比如我们在第3章里写的程序),又该怎么办?

4.2.2 测试Web安全

Spring Security能让你非常方便地测试安全加固后的Web应用程序。为了利用这点优势,你必须在项目里添加Spring Security的测试模块。要在Gradle里做到这一点,你需要的就是以下testCompile依赖:

testCompile("org.springframework.security:spring-security-test")

如果你用的是Maven,则添加以下<dependency>

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-test</artifactId>
  <scope>test</scope>
</dependency>

应用程序的Classpath里有了Spring Security的测试模块之后,只需在创建MockMvc实例时运用Spring Security的配置器。

@Before
public void setupMockMvc() {
  mockMvc = MockMvcBuilders
    .webAppContextSetup(webContext)
    .apply(springSecurity())
    .build();
}

springSecurity()方法返回了一个Mock MVC配置器,为Mock MVC开启了Spring Security支持。只需像上面这样运用就行了,Spring Security会介入MockMvc上执行的每个请求。具体的安全配置取决于你如何配置Spring Security(或者Spring Boot如何自动配置Spring Security)。在阅读列表这个应用程序里,我们在第3章里创建SecurityConfig.java时,配置也是如此。

springSecurity()方法

springSecurity()SecurityMockMvcConfigurers的一个静态方法,考虑到可读性,我已经将其静态导入。

开启了Spring Security之后,在请求主页的时候,我们便不能只期待HTTP 200响应。如果请求未经身份验证,我们应该期待重定向到登录页面:

@Test
public void homePage_unauthenticatedUser() throws Exception {
  mockMvc.perform(get("/"))
    .andExpect(status().is3xxRedirection())
    .andExpect(header().string("Location",
                               "http://localhost/login"));
}

但是我们又该如何发起一个经过身份验证的请求呢?Spring Security提供了两个注解。

  • @WithMockUser:加载安全上下文,其中包含一个UserDetails,使用了给定的用户名、密码和授权。
  • @WithUserDetails:根据给定的用户名查找UserDetails对象,加载安全上下文。

在这两种情况下,Spring Security的安全上下文都会加载一个UserDetails对象,添加了该注解的测试方法在运行过程中都会使用该对象。@WithMockUser注解是两者里比较基础的那个,允许显式声明一个UserDetails,并加载到安全上下文:

@Test
@WithMockUser(username="craig",
              password="password",
              roles="READER")
public void homePage_authenticatedUser() throws Exception {
  ...
}

如你所见,@WithMockUser绕过了对UserDetails对象的正常查询,用给定的值创建了一个UserDetails对象取而代之。在简单的测试里,这就够用了。但我们的测试需要Reader(实现了UserDetails)而非@WithMockUser创建的通用UserDetails。为此,我们需要@WithUserDetails

@WithUserDetails注解使用事先配置好的UserDetailsService来加载UserDetails对象。回想一下第3章,我们配置了一个UserDetailsService Bean,它会根据给定的用户名查找并返回一个Reader对象。太完美了!所以我们要为测试方法添加@WithUserDetails注解,如代码清单4-4所示。

代码清单4-4 测试带有用户身份验证的安全加固方法

原书P85代码

文字

Uses “craig” user :使用craig用户

Sets up expected Reader:配置期望的Reader

Performs GET request:发起GET请求

在代码清单4-4里,我们通过@WithUserDetails注解声明要在测试方法执行过程中向安全上下文里加载craig用户。Reader会放入模型,该测试方法先创建了一个期望的Reader对象,后续可以用来进行比较。随后GET请求发起,也有了针对视图名和模型内容的断言,其中包括名为reader的模型属性。

同样,此处没有启动Servlet容器来运行这些测试,Spring的Mock MVC取代了实际的Servlet容器。这样做的好处是测试方法运行相对较快。因为不需要等待服务器启动,而且不需要打开Web浏览器发送表单,所以测试比较简单快捷。

不过,这并不是一个完整的测试。它比直接调用控制器方法要好,但它并没有真的在Web浏览器里执行应用程序,验证呈现出的视图。为此,我们需要启动一个真正的Web服务器,用真实浏览器来访问它。让我们来看看Spring Boot如何启动一个真实的Web服务器来帮助测试。

##4.3 测试运行中的应用程序

说到测试Web应用程序,我们还没接触实质内容。在真实的服务器里启动应用程序,用真实的Web浏览器访问它,这样比使用模拟的测试引擎更能展现应用程序在用户端的行为。

但是,用真实的Web浏览器在真实的服务器上运行测试会很麻烦。虽然构建时的插件能把应用程序部署到Tomcat或者Jetty里,但它们配置起来多有不便。而且测试这么多,几乎不可能隔离运行,也很难不启动构建工具。

然而Spring Boot找到了解决方案。它支持将Tomcat或Jetty这样的嵌入式Servlet容器作为运行中的应用程序的一部分,可以运用相同的机制,在测试过程中用嵌入式Servlet容器来启动应用程序。

Spring Boot的@WebIntegrationTest注解就是这么做的。在测试类上添加@WebIntegrationTest注解,可以声明你不仅希望Spring Boot为测试创建应用程序上下文,还要启动一个嵌入式的Servlet容器。一旦应用程序运行在嵌入式容器里,你就可以发起真实的HTTP请求,断言结果了。

举例来说,考虑一下代码清单4-5里的那段简单的Web测试。这里采用@WebIntegrationTest,在服务器里启动了应用程序,以Spring的RestTemplate对应用程序发起HTTP请求。

代码清单4-5 测试运行在服务器里的Web应用程序

原书P86代码

文字

Runs test in server:在服务器里运行测试

Performs GET request:发起GET请求

Asserts HTTP 404 (not found) response:判断HTTP 404 (not found)响应

虽然这个测试非常简单,但足以演示如何使用@WebIntegrationTest在服务器里启动应用程序。要判断实际启动的服务器究竟是哪个,可以遵循在命令行里运行应用程序时的逻辑。默认情况下,会有一个监听8080端口的Tomcat启动。但是,如果Classpath里有的话,Jetty或者Undertow也能启动这些服务器。

测试方法的主体部分假设应用程序已经运行,监听了8080端口。它使用了Spring的RestTemplate对一个不存在的页面发起请求,判断服务器的响应是否为HTTP 404 (not found)。如果返回了其他响应,则测试失败。

4.3.1 用随机端口启动服务器

前面提到过,此处的默认行为是启动服务器监听8080端口。在一台机器上一次只运行一个测试的话,这没什么问题,因为没有其他服务器监听8080端口。但如果你和我一样,本机总是有其他服务器在监听8080端口,那该怎么办?这时测试会失败,因为端口冲突,服务器启动不了。一定要有更好的办法才行。

幸运的是,让Spring Boot在随机选择的端口上启动服务器很方便。一种办法是将server.port属性设置为0,让Spring Boot选择一个随机的可用端口。@WebIntegrationTestvalue属性接受一个String数组,数组中的每项都是键值对,形如name=value,用来设置测试中使用的属性。要设置server.port,你可以这样做:

@WebIntegrationTest(value={"server.port=0"})

另外,因为只要设置一个属性,所以还能有更简单的形式:

@WebIntegrationTest("server.port=0")

通过value属性来设置属性通常还算方便。但@WebIntegrationTest还提供了一个randomPort属性,更明确地表示让服务器在随机端口上启动。你可以将randomPort设置为true,启用随机端口:

@WebIntegrationTest(randomPort=true)

既然我们在随机端口上启动了服务器,就需要在发起Web请求时确保使用正确的端口。此时的getForObject()方法在URL里硬编码了8080端口。如果端口是随机选择的,那在构造请求时又该怎么确定正确的端口呢?

首先,我们需要以实例变量的形式注入选中的端口。为了方便,Spring Boot将local.server.port的值设置为了选中的端口。我们只需使用Spring的@Value注解将其注入即可:

@Value("${local.server.port}")
private int port;

有了端口之后,只需对getForObject()稍作修改,使用这个port就好了:

rest.getForObject(
    "http://localhost:{port}/bogusPage", String.class, port);

这里我们在URL里把硬编码的8080改为{port}占位符。在getForObject()调用里把port属性作为最后一个参数传入,就能确保该占位符被替换为注入port的值了。

4.3.2 使用Selenium测试HTML页面

RestTemplate对于简单的请求而言使用方便,是测试REST端点的理想工具。但是,就算它能对返回HTML页面的URL发起请求,也不方便对页面内容或者页面上执行的操作进行断言。结果HTML里的内容最好能够精确判断(这种测试很脆弱)。不过你无法轻易判断页面上选中的内容,或者执行诸如点击链接或提交表单这样的操作。

对于HTML应用程序测试,有一个更好的选择——Selenium(www.seleniumhq.org),它的功能远不止提交请求和获取结果。它能实际打开一个Web浏览器,在浏览器的上下文中执行测试。Selenium尽量接近手动执行测试,但与手工测试不同。Selenium的测试是自动的,而且可以重复运行。

为了用Selenium测试阅读列表应用程序,让我们先写一个测试来获取首页,为新书填写表单,提交表单,随后判断返回的页面里是否包含新添加的图书。

首先需要把Selenium作为测试依赖添加到项目里:

testCompile("org.seleniumhq.selenium:selenium-java:2.45.0")

现在就可以编写测试了。代码清单4-6是一个基本的Selenium测试模板,使用了Spring Boot的@WebIntegrationTest

代码清单4-6 在Spring Boot里使用Selenium测试的模板

原书P88~89代码

文字

Starts on a random port:用随机端口启动

Injects the port:注入端口号

Sets up Firefox driver:配置Firefox驱动

Shuts down browser:关闭浏览器

和之前更简单的Web测试一样,这个类添加了@WebIntegrationTest注解,将randomPort设置为true,这样应用程序启动后会运行一个监听随机端口的服务器。同样,端口号注入port属性,这样我们就能用它来构造指向运行中应用程序的URL了。

静态方法openBrowser()会创建一个FirefoxDriver的实例,它将打开Firefox浏览器(需要在运行测试的服务器上安装该浏览器)。我们的测试方法将通过FirefoxDriver实例来执行浏览器操作。在页面上查找元素时,FirefoxDriver配置了10秒的等候时间(以防元素加载过慢)。

测试执行完毕,我们需要关闭Firefox浏览器。因此要在closeBrowser()里要调用FirefoxDriver实例的quit()方法,关闭浏览器。

选择浏览器

虽然我们用Firefox进行了测试,但Selenium还提供了不少其他浏览器的驱动,包括IE、Google的Chrome,还有Apple的Safari。测试可以使用其他浏览器。你也可以使用你想支持的各种浏览器,这也许也是个不错的想法。

现在可以开始编写测试方法了,给你提个醒,我们想要加载首页,填充并发送表单,然后判断登录的页面是否包含刚刚添加的新书。代码清单4-7演示了如何用Selenium实现这个功能。

代码清单4-7 用Selenium测试阅读列表应用程序

原书P89~90代码

文字

Fetches the home page:获取主页

Asserts an empty book list:判断图书列表是否为空

Fills in and submits form:填充并发送表单

Asserts new book in list:判断列表中是否包含新书

该测试方法所做的第一件事是使用FirefoxDriver来发起GET请求,获取阅读列表的主页,随后查找页面里的一个<div>元素,从它的文本里判断列表里没有图书。

接下来的几行查找表单里的元素,使用驱动的sendKeys()方法模拟敲击键盘事件(实际上就是用给定的值填充那些表单域)。最后,找到<form>元素并提交。

提交的表单经处理后,浏览器就会跳到一个页面,上面的列表包含了新添加的图书。因此最后几行查找列表里的<dt><dd>元素,判断其中是否包含测试表单里提交的数据。

运行测试时,你会看到浏览器打开,加载阅读列表应用程序。如果够仔细,你还会看到填充表单的过程,就好像幽灵在操作,当然,并没有幽灵使用你的应用程序——这只是一个测试。

这个测试里最值得注意的是,@WebIntegrationTest可以为我们启动应用程序和服务器,这样Selenium才可以用Web浏览器执行测试。但真正有趣的是你可以使用IDE的测试功能来运行测试,随便你想跑几次都行,无需依赖构建过程中的某些插件启动服务器。

要是你觉得使用Selenium进行测试很实用,可以阅读Yujun Liang和Alex Collins的Selenium WebDriver in Practice(http://manning.com/liang/),该书更深入地讨论了Selenium测试的细节。

4.4 小结

测试是开发高质量软件的重要一环。没有好的测试,你永远无法保证应用程序能像你期望的那样运行。

单元测试专注于单一组件或组件中的一个方法,此处并不一定要使用Spring。Spring提供了一些优势和技术——松耦合、依赖注入和接口驱动设计。这些都简化了单元测试的编写。但Spring不用直接涉足单元测试。

集成测试会涉及众多组件,这时就需要Spring帮忙了。实际上,如果Spring在运行时负责拼装那些组件,那么Spring在集成测试里同样应该肩负这一职责。

Spring Framework以JUnit类运行器的方式提供了集成测试支持,JUnit类运行器会加载Spring应用程序上下文,把上下文里的Bean注入测试。Spring Boot在Spring的集成测试之上又增加了配置加载器,以Spring Boot的方式加载应用程序上下文,包括了对外置属性的支持和Spring Boot日志。

Spring Boot还支持容器内测试Web应用程序,让你能用和生产环境一样的容器启动应用程序。这样一来,测试在验证应用程序行为的时候,会更加接近真实的运行环境。

此时我们已经构建了一个相当完整的应用程序(虽然有点简单),它利用Spring Boot的起步依赖和自动配置来处理低级工作,让我们专心开发应用程序。我们也看到了如何使用Spring Boot的支持来测试应用程序。在后续几章里,我们会看到一些不同的东西,了解让Spring Boot应用程序开发更加简单的Groovy。在第5章,我们会先了解Grails框架的一些特性,看看它们在Spring Boot中的用途。

0
青年IT男

个人从事金融行业,就职过易极付、思建科技等重庆一流技术团队,目前就职于某网约车平台负责整个支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。

评论已关闭。

This site is protected by wp-copyrightpro.com