无论您是遵循传统的测试金字塔还是采用诸如“测试蜂窝”这样的较新方法,都应该在开发过程中的某个时候开始编写集成测试用例。

您可以编写不同类型的集成测试。从持久性测试开始,您可以检查组件之间的交互,也可以模拟调用外部服务。本文将讨论后一种情况。

在谈论WireMock之前,让我们从一个典型的例子开始。

ChuckNorrisService

我们有一个简单的API,用于手动测试。在“业务”类意外是,它可以调用外部API。它使用Spring 框架提供功能的。没什么特别的。我多次看到的是模拟RestTemplate并返回一些预先确定的答案的测试。该实现可能如下所示:

  1. @Service
  2. public class ChuckNorrisService{
  3. ...
  4. public ChuckNorrisFact retrieveFact() {
  5. ResponseEntity<ChuckNorrisFactResponse> response = restTemplate.getForEntity(url, ChuckNorrisFactResponse.class);
  6. return Optional.ofNullable(response.getBody()).map(ChuckNorrisFactResponse::getFact).orElse(BACKUP_FACT);
  7. }
  8. ...
  9. }

在检查成功案例的常规单元测试旁边,将至少有一项覆盖HTTP错误码的测试用例,即4xx或5xx状态代码:

  1. @Test
  2. public void shouldReturnBackupFactInCaseOfError() {
  3. String url = "http://localhost:8080";
  4. RestTemplate mockTemplate = mock(RestTemplate.class);
  5. ResponseEntity<ChuckNorrisFactResponse> responseEntity = new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE);
  6. when(mockTemplate.getForEntity(url, ChuckNorrisFactResponse.class)).thenReturn(responseEntity);
  7. ChuckNorrisService service = new ChuckNorrisService(mockTemplate, url);
  8. ChuckNorrisFact retrieved = service.retrieveFact();
  9. assertThat(retrieved).isEqualTo(ChuckNorrisService.BACKUP_FACT);
  10. }

看起来还不错吧?响应实体返回503错误代码,我们的服务不会崩溃。所有测试都是绿色通过的,我们可以部署我们的应用程序。

不幸的是,Spring的RestTemplate不能这样使用。方法签名getForEntity给了我们很小的提示。它指出throws RestClientException。这就是mock的RestTemplate与实际实现不同的地方。我们将永远不会收到ResponseEntity带有4xx或5xx状态代码的。RestTemplate将抛出的子类RestClientException。通过查看类的层次结构,我们可以对可能抛出的结果有一个很好的印象:

因此,让我们看看如何使这项测试更好。

WireMock进行拯救

WireMock通过启动模拟服务器并返回将其配置为返回的答案来模拟Web服务。得益于出色的DSL,它很容易集成到您的测试中,并且模拟请求也很简单。

对于JUnit 4,有一个WireMockRule有助于启动停止服务器的工具。对于JUnit 5,大概需要自己做一个这样的工具。当您检查示例项目时,您可以找到ChuckNorrisServiceIntegrationTest。这是基于JUnit 4的SpringBoot测试。让我们看一下。

最重要的部分是ClassRule:

  1. @ClassRule
  2. public static WireMockRule wireMockRule = new WireMockRule();

如前所述,这将启动和停止WireMock服务器。您也可以像往常一样使用该规则Rule来启动和停止每个测试的服务器。对于我们的测试,这不是必需的。

接下来,您将看到几种configureWireMockFor...方法。这些包含WireMock何时返回答案的说明。将WireMock配置分为几种方法并从测试中调用它们是我使用WireMock的方法。当然,您可以在一个@Before方法中设置所有可能的请求。对于正确使用的Demo,我们这样做:

  1. public void configureWireMockForOkResponse(ChuckNorrisFact fact) throws JsonProcessingException {
  2. ChuckNorrisFactResponse chuckNorrisFactResponse = new ChuckNorrisFactResponse("success", fact);
  3. stubFor(get(urlEqualTo("/jokes/random"))
  4. .willReturn(okJson(OBJECT_MAPPER.writeValueAsString(chuckNorrisFactResponse))));
  5. }

所有方法都是从静态导入的com.github.tomakehurst.wiremock.client.WireMock。如您所见,我们将HTTP GET存入路径/jokes/random并返回JSON对象。该okJson()方法只是带有JSON内容的200响应的简写。对于错误情况,代码甚至更简单:

  1. private void configureWireMockForErrorResponse() {
  2. stubFor(get(urlEqualTo("/jokes/random"))
  3. .willReturn(serverError()));
  4. }

如您所见,DSL使阅读说明变得容易。将WireMock放置在适当的位置,我们可以看到我们先前的实现不起作用,因为RestTemplate引发了异常。因此,我们必须调整代码:

  1. public ChuckNorrisFact retrieveFact() {
  2. try {
  3. ResponseEntity<ChuckNorrisFactResponse> response = restTemplate.getForEntity(url, ChuckNorrisFactResponse.class);
  4. return Optional.ofNullable(response.getBody()).map(ChuckNorrisFactResponse::getFact).orElse(BACKUP_FACT);
  5. } catch (HttpStatusCodeException e){
  6. return BACKUP_FACT;
  7. }
  8. }

这已经涵盖了WireMock的基本用例。配置请求的答案,执行测试,检查结果,so easy,就这么简单。尽管如此,在云环境中运行测试时通常会遇到一个问题。让我们看看我们能做什么。

动态端口上的WireMock

您可能已经注意到,项目中的集成测试包含一个

ApplicationContextInitializer类,并且其@TestPropertySource注释会覆盖实际API的URL。那是因为我想在随机端口上启动WireMock。当然,您可以为WireMock配置一个固定端口,并在测试中将此端口用作常量来处理。但是,如果您的测试在某些云提供商的基础架构上运行,则无法确定该端口是否可用。因此,我认为随机端口更好。

不过,在Spring应用程序中使用属性时,我们必须以某种方式将随机端口传递给我们的服务。或者,如您在示例中看到的那样,覆盖URL。这就是为什么我们使用ApplicationContextInitializer。我们将动态分配的端口添加到应用程序上下文中,然后可以使用属性来引用它${wiremock.port}。这里唯一的缺点是我们现在必须使用ClassRule。否则,我们无法在初始化Spring应用程序之前访问端口。

解决了此问题后,让我们看一下涉及HTTP调用的一个常见问题。

超时时间

WireMock提供了更多的响应可能性,而不仅仅是对GET请求的简单答复。经常被遗忘的另一个测试案例是测试超时。开发人员往往会忘记在RestTemplate设置超时URLConnections。如果没有超时,则两者都将等待无限量的时间来进行响应。在最好的情况下,在最坏的情况下,所有线程都将等待永远不会到达的响应。

因此,我们应该添加一个模拟超时的测试。当然,我们也可以使用Mockito模拟来创建延迟,但是在这种情况下,我们将再次猜测RestTemplate的行为。使用WireMock模拟延迟非常简单:

  1. private void configureWireMockForSlowResponse() throws JsonProcessingException {
  2. ChuckNorrisFactResponse chuckNorrisFactResponse = new ChuckNorrisFactResponse("success", new ChuckNorrisFact(1L, ""));
  3. stubFor(get(urlEqualTo("/jokes/random"))
  4. .willReturn(
  5. okJson(OBJECT_MAPPER.writeValueAsString(chuckNorrisFactResponse))
  6. .withFixedDelay((int) Duration.ofSeconds(10L).toMillis())));
  7. }

withFixedDelay()期望一个表示毫秒的int值。我更喜欢使用Duration或至少一个表示该参数表示毫秒的常量,而不必每次写代码都需要看一下代码注释。

设置超时RestTemplate并添加响应的测试后,我们可以看到RestTemplate抛出ResourceAccessException。因此,我们可以调整catch块以捕获此异常和,HttpStatusCodeException或者仅捕获两者的超类:

  1. public ChuckNorrisFact retrieveFact() {
  2. try {
  3. ResponseEntity<ChuckNorrisFactResponse> response = restTemplate.getForEntity(url, ChuckNorrisFactResponse.class);
  4. return Optional.ofNullable(response.getBody()).map(ChuckNorrisFactResponse::getFact).orElse(BACKUP_FACT);
  5. } catch (RestClientException e){
  6. return BACKUP_FACT;
  7. }
  8. }

现在,我们已经很好地介绍了执行HTTP请求时最常见的情况,并且可以确定我们正在测试接近真实条件的条件。

为什么不?

HTTP集成测试的另一个选择是Hoverfly。它的工作原理类似于WireMock,但我更喜欢后者。原因是在运行包含浏览器的端到端测试时,WireMock也非常有用。Hoverfly(至少是Java库)受JVM代理的限制。这可能使它比WireMock更快,但是当例如某些JavaScript代码开始起作用时,它根本不起作用。当您的浏览器代码也直接调用其他一些服务时,WireMock启动Web服务器这一功能非常有用。然后,您也可以使用WireMock来mock它们,并编写例如Selenium测试。

结论

本文可以向您展示两件事:

  • 集成测试的重要性
  • WireMock是个非常不错的测试框架

当然,这两个主题都可以写出非常多的文章。尽管如此,还是分享了如何使用WireMock及其功能。在以后的学习路上多去阅读他们的文档,然后尝试更多其他功能,例如利用WireMock来进行身份验证。


使用WireMock进行更好的集成测试的更多相关文章

  1. 应用程序框架实战十三:DDD分层架构之我见

    前面介绍了应用程序框架的一个重要组成部分——公共操作类,并提供了一个数据类型转换公共操作类作为示例进行演示.下面准备介绍应用程序框架的另一个重要组成部分,即体系架构支持.你不一定要使用DDD这样的架构 ...

  2. express-4 质量保证(1)

    QA 在Web开发中,质量可以分解为四个维度: 到达率: 到达率是指产品的市场普及程度,即查看网站或使用服务的人数.到达率和盈利能力是正相关关系:访问网站的人越多,购买产品或服务的人就越多.从开发的角 ...

  3. 应用程序框架实战十三:DDD分层架构之我见(转)

    前面介绍了应用程序框架的一个重要组成部分——公共操作类,并提供了一个数据类型转换公共操作类作为示例进行演示.下面准备介绍应用程序框架的另一个重要组成部分,即体系架构支持.你不一定要使用DDD这样的架构 ...

  4. DDD分层架构之我见

    DDD分层架构之我见 前面介绍了应用程序框架的一个重要组成部分——公共操作类,并提供了一个数据类型转换公共操作类作为示例进行演示.下面准备介绍应用程序框架的另一个重要组成部分,即体系架构支持.你不一定 ...

  5. 如何使用TDD和React Testing Library构建健壮的React应用程序

    如何使用TDD和React Testing Library构建健壮的React应用程序 当我开始学习React时,我努力的一件事就是以一种既有用又直观的方式来测试我的web应用程序. 每次我想测试它时 ...

  6. IntelliJ IDEA:Field injection is not recommended

    使用IntelliJ IDEA进行开发的时候,code analyze的时候会出现提示“Field injection is not recommended”. stackoverflow上有篇回答: ...

  7. Java生鲜电商平台-微服务架构概述

    Java生鲜电商平台-微服务架构概述 单体架构存在的问题 在传统的软件技术架构系统中,基本上将业务功能集中在单一应用内,或者是单一进程中.尽管现代化的软件架构理论以及设计原则已推广多年,但实际技术衍化 ...

  8. [Abp vNext 源码分析] - 18. 单元测试

    简介 ABP vNext 框架使用 xUnit 作为单元测试组件,官方的所有模块都编写了大量的 单元/集成测试 确保功能正常.由于 ABP vNext 模块化系统的原因,开发人员在建立单元测试项目的时 ...

  9. ASP.NET Core 中文文档 第五章 测试(5.2)集成测试

    原文: Integration Testing 作者: Steve Smith 翻译: 王健 校对: 孟帅洋(书缘) 集成测试确保应用程序的组件组装在一起时正常工作. ASP.NET Core支持使用 ...

随机推荐

  1. 百度ERNIE 2.0强势发布!16项中英文任务表现超越BERT和XLNet

    2019年3月,百度正式发布NLP模型ERNIE,其在中文任务中全面超越BERT一度引发业界广泛关注和探讨. 今天,经过短短几个月时间,百度ERNIE再升级.发布持续学习的语义理解框架ERNIE 2. ...

  2. 解决移动端touch事件与click冲突的问题

    最简单的办法,就只绑定一个事件不就行了: 第二种,我觉得和第一种也没啥区别.. const Button = document.getElementById("targetButton&qu ...

  3. 练习Markdown基本语法

    这是一级标题 二级标题 三级标题 我就说一点(数字+英文句点.) 第二 在行首增加*或-,就会有下面的效果 嘿 嘿 嘿 这一部分是插入图片和引用 插入图片 用感叹号+[]+括号 直接复制粘贴~ 引用 ...

  4. 第四十章 POSIX条件变量

    条件变量 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中.这种情况就需要用到条件 ...

  5. 【RocketMQ源码学习】- 3. Client 发送同步消息

    本文较长,代码后面给了方法简图,希望给你帮助 发送的方式 同步发送 异步发送 消息的类型 普通消息 顺序消息 事务消息 发送同步消息的时序图 为了防止读者朋友嫌烦,可以看下时序图,后面我也会给出方法的 ...

  6. 羞,Java 字符串拼接竟然有这么多姿势

    二哥,我今年大二,看你分享的<阿里巴巴 Java 开发手册>上有一段内容说:"循环体内,拼接字符串最好使用 StringBuilder 的 append 方法,而不是 + 号操作 ...

  7. java编程思想第四版第五章总结

    1. 构造器 构造器的一个重要的作用: 保证对象被使用之前初始化了. 构造器是一种特殊类型的方法, 因为他没有返回值.这与返回值为空(void)明显不同.对于空返回值,尽管方法本身不会自动返回什么, ...

  8. js json对象操作

    参数解析: sourcelist是json对象[{id:1,name:张三}] targetlist是json对象[{roomid:1}] 返回值list是json对象[{id:1,name:张三,  ...

  9. 树莓派3B/3B+和4B安装OpenCV教程

    安装前准备 在树莓派上拓展文件系统 如果你使用的树莓派为新装的系统,那么第一件事情就是扩展文件系统,以包括microSD卡上的所有空间. 具体步骤如下: 1.在树莓派终端(或者SSH)上输入: $ s ...

  10. vc在x64体系的一般传参数方式

    前篇分析过在objc中函数调用传参的一般方式,本篇分析vc在x64体系中的一般传参方式.手头上因为没有64位的vc编译器,只好用windbg看ms自身的函数是怎么样调用的. 首先看两个再熟悉不过的ap ...