燃爆上海 5·23-24,AICon 大模型实战风暴,50+ 干货一网打尽,100% 日程上线 了解详情
写点什么

使用 WireMock 模拟 HTTP API 服务

  • 2016-11-29
  • 本文字数:5563 字

    阅读完需:约 18 分钟

要点:

  • 使用 WireMock 可以极大减少创建连线测试桩所需要的时间
  • 在 JUnit 里使用 WireMock 可以更容易地编写各种测试用例
  • 我们会给出一个单独使用 WireMock 的例子
  • 我们还会为手动测试人员介绍如何使用 WireMock 的 UI
  • 还有其它一些内容

开场:我所知道的连线模拟工具

在我的职业生涯中,我有幸在很多大公司工作过。在工作过程中,我发现在开发上存在的一个普遍现象:开发团队总是重复开发一些相似的工具。我亲眼所见两个不同银行的开发团队在公司内部(基于不同的代码库)开发出三个以上基于 HTTP 的连线模拟工具(有时候也被叫作服务可视化工具)。我还在一个媒体公司看到它们同一楼层的几个团队居然开发了五个这种工具。还有一个航空企业,它开发了至少一种这样的工具。

这些基于 HTTP 的模拟工具主要有两方面的用途:

  1. 在自动化测试里,它们提供了可以用于创建远程虚拟服务的接口,这样就可以在 CI(持续集成)环境里构建系统,比如 TeamCity 或 Jenkins。
  2. 它们提供了 Web 界面,那些对技术细节不是很了解的测试人员可以在上面做一些探索性的测试。

因为同一个工具同时被用于自动化测试和手动测试,自然会有很多重叠的地方。测试人员可以使用开发人员创建的桩,这样有助于他们之间的沟通。同时这样也会减少在创建测试桩上所做的工作,相比测试人员和开发人员要分别创建自己的桩,这种方式避免了很多重复性工作。不过有些团队没有测试人员,所以他们只需要编程接口而不需要 Web 界面。

业界使用现成的工具

让我们从日志和数据存储方面来一窥整个行业对这些工具的使用情况。

如果有必要,你可以在一两天内轻松实现一个简单的日志框架。不过经验告诉我们,从头写一个日志框架并不是一个好主意。所以我们会使用 logback、slf4j 或其它一些现成的日志框架。

日志还只是个简单的问题,如果我们要考虑更复杂的数据存储,我们只能使用市场上现成的解决方案,比如 Oracle、MongoDB 或 Neo4J,具体要根据实际需要来选择。

我们举个测试框架的例子。你不会为每个项目从头写一个测试框架,所以你会使用 JUnit 或者其它任何一个可用的框架,有时候可能会基于这些框架做一些定制开发。

总的来说,针对那些常见的问题,总有一些现成的解决方案。

WireMock:一个现成的连线模拟(虚拟服务)工具

在预打包的测试框架解决方案方面,连线模拟工具(虚拟服务)会是一个更好的选择。WireMock 就是这样一个备受关注的开源框架,它使用 Java 编写,可以在 JUnit 里使用,也可以作为独立的 HTTP 服务运行。

在 JUnit 里使用 WireMock

假设我们有一个天气预报程序,它通过调用 HTTP API 从 forecast.io 获取伦敦的风速数据。预报程序获得的数据是 JSON 字符串,需要对它们进行解析,抽取出当前风速,再通过 REST 接口返回给用户。

我们现在使用 JUnit 编写测试用例,需要使用桩来模拟 forecast.io 提供的 Web 服务:

复制代码
public class WeatherApplicationTest {
@Rule
public WireMockRule forecastIoService = new WireMockRule();
}

然后我们把预报程序加入到测试用例里。预报程序的 start 和 stop 方法分别放在 JUnit 的 setup 和 teardown 方法里。我们先测试一个正常的用例 servesWindSpeedBasedOnForecastIoResponse:

复制代码
public class WeatherApplicationTest {
@Rule
public WireMockRule forecastIoService = new WireMockRule();
private WeatherApplication weatherApplication = new WeatherApplication();
@Before
public void setUp() {
weatherApplication.start();
}
@After
public void tearDown() {
weatherApplication.stop();
}
@Test
public void servesWindSpeedBasedOnForecastIoResponse(){
// TODO
}
}

成功的测试用例

测试过程是这样的:先发送请求到预报程序,预报程序从 forecast.io 获取响应数据,然后对其进行解析,加入后缀,再把它返回给用户。

首先我们使用断言检查我们想要的风速数据格式是以英里每小时(mph)为后缀的:

复制代码
@Test
public void servesWindSpeedBasedOnForecastIoResponse() {
assertEquals("12.34mph", content.toString());
}

为了得到这些数据,我们使用 Apache HTTP 客户端向应用程序发起请求:

复制代码
@Test
public void servesWindSpeedBasedOnForecastIoResponse() throws IOException {
Content content = Request.Get("http://localhost:" + weatherApplication.port() + "/wind-speed")
.execute()
.returnContent();
assertEquals("12.34mph", content.toString());
}

现在,应用程序将向 forecast.io 发起获取预报数据的请求,这个时候就要使用桩来模拟真实的 API:

复制代码
@Test
public void servesWindSpeedBasedOnForecastIoResponse() throws IOException {
forecastIoService.stubFor(get(urlEqualTo(
"/forecast/e67b0e3784104669340c3cb089412b67/51.507253,-0.127755")).willReturn(aResponse().withBody(
"{\"currently\":{\"windSpeed\":12.34}}")
));
Content content = Request.Get("http://localhost:" +
weatherApplication.port() + "/wind-speed")
.execute()
.returnContent();
assertEquals("12.34mph", content.toString());
}

我们告诉 WireMock(forecastIoService 对象)为请求“/forecast/e67b0e3784104669340c3cb089412b67/51.507253,-0.127755”创建一个桩,并把指定的 json 串返回。

真实的 forecast.io API 返回的响应数据大概是 2kb,不过为了简单起见,这里只包含我们关心的字节数。这篇文章主要是为了介绍如何使用 WireMock,而不是要讨论如何做测试( TDD 测试金字塔模型)。

失败的测试用例

我们已经知道如何创建一个成功的测试用例,现在让我们看看如何创建一个失败的测试用例。我们不想在 forecast.io 出现问题时抛出异常,而是要把 503 错误码返回给用户。

这次我们仍然使用 /windspeed 这个 URL 端点和相同的 forecast.io 测试桩。我们创建测试桩和请求消息,它将返回一个内部错误码(HTTP 500)。我们要确保用户收到的是一个服务不可用的错误消息和错误码(HTTP 503),而不是异常的堆栈信息:

复制代码
@Test
public void reportsErrorWhenForecastIoReturnsANonSuccessfulResponse()
throws IOException {
forecastIoService.stubFor(get(urlEqualTo("/forecast/e67b0e3784104669340c3cb089412b67/51.507253,-0.127755"))
.willReturn(aResponse().withStatus(SC_INTERNAL_SERVER_ERROR)));
HttpResponse httpResponse = Request.Get("http://localhost:" + weatherApplication.port() + "/wind-speed")
.execute()
.returnResponse();
assertEquals(503,httpResponse.getStatusLine().getStatusCode());
assertEquals("ERROR",IOUtils.toString(httpResponse.getEntity().getContent()));
}

这是一个非常简单的例子,不过它向我们展示了如何使用 WireMock 进行成功和失败用例的自动化测试。

整个测试用例的最终代码看起来是这样的:

复制代码
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.fluent.Content;
import org.apache.http.client.fluent.Request;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static
javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static junit.framework.TestCase.assertEquals;
public class WeatherApplicationTest {
@Rule
public WireMockRule forecastIoService = new WireMockRule();
private WeatherApplication weatherApplication = new WeatherApplication();
@Before
public void setUp() {
weatherApplication.start();
}
@After
public void tearDown() {
weatherApplication.stop();
}
@Test
public void servesWindSpeedBasedOnForecastIoResponse() throws IOException {
forecastIoService.stubFor(get(urlEqualTo(
"/forecast/e67b0e3784104669340c3cb089412b67/51.507253,-0.127755"))
.willReturn(aResponse().withBody("{\"currently\":{\"windSpeed\":12.34}}"
)));
Content content = Request.Get("http://localhost:" +
weatherApplication.port() + "/wind-speed")
.execute()
.returnContent();
assertEquals("12.34mph", content.toString());
}
@Test
public void reportsErrorWhenForecastIoReturnsANonSuccessfulResponse()
throws IOException {
forecastIoService.stubFor(get(urlEqualTo(
"/forecast/e67b0e3784104669340c3cb089412b67/51.507253,-0.127755"))
.willReturn(aResponse().withStatus(SC_INTERNAL_SERVER_ERROR)));
HttpResponse httpResponse = Request.Get("http://localhost:" +
weatherApplication.port() + "/wind-speed")
.execute()
.returnResponse();
assertEquals(503, httpResponse.getStatusLine().getStatusCode());
assertEquals("ERROR",
IOUtils.toString(httpResponse.getEntity().getContent()));
}
}

上面的例子只反应了这个工具所有功能的一小方面。它的所有代码可以在我的Github 主页找到,更多的细节可以查看 WireMock 文档

手动测试

在开场白部分,我们已经介绍了连线模拟工具的两个主要用途。第一个是作为自动化测试的模拟组件,这个我们已经举过例子了。接下来,我们来看看如何使用图形化 Web 控制台界面做手动测试。

WireMock 的控制台

WireMock 可以通过命令行来调用,也可以通过 Postman 或 cURL 这样的工具来做设置操作。可以往指定的 URL 发送一个 HTTP POST 请求来新建一个桩,比如往 http://:/__admin/mappings/new 发送请求

复制代码
{
"request": {
"method": "GET",
"url": "/some/thing"
},
"response": {
"status": 200,
"body": "Hello world!",
"headers": {
"Content-Type": "text/plain"
}
}
}

,这个就相当于在 Java 里执行了以下代码:

复制代码
stubFor(get(urlEqualTo("/some/thing"))
.willReturn(aResponse()
.withHeader("Content-Type", "text/plain")
.withBody("Hello world!")));

解决手动测试的问题

如果你的测试人员对技术细节不是很了解或者他们不希望花太多时间在技术细节上,那你可以帮助他们把精力集中在他们擅长的手动测试上。

首先,可以基于 WireMock 开发一个 Web 界面或 Swing 图形界面。经验告诉我,这样可能需要 1 到 10 个开发人员花费最多 12 个月的时间。

还有一种选择,就是使用现成的产品,比如专门为敏捷团队打造的 Traffic Parrot 。Traffic Parrot 是一款带有 Web 界面的连线模拟工具,一般敏捷开发团队会用到它。开发人员可以使用 WireMock 创建测试用例,再把它们的定义导出到 Traffic Parrot。测试人员可以使用 Traffic Parrot 的 Web 界面为手动测试创建测试桩。因为工作原因,我曾经在多个公司使用这个工具,现在我收集了一些常用的场景,并把它们共享出来。也许它们可以为大家节省一些时间,避免那些我曾经犯过的错误。

模拟场景

在我的咨询顾问职业生涯中,我遇到了另一类有关虚拟服务的问题,就是经常出现一个用例需要用到多个端点或需要返回多种响应的情况。例如,如果你要为用户提供基于位置的当前风速数据,需要调用两个 Web 服务,一个用于获取 GPS 坐标信息,另一个用于获取天气预报数据。在 JUnit 里看起来是这样的:

复制代码
gpsService.stubFor(get(urlEqualTo("/what-are-my-coordinates"))
.willReturn(aResponse().withBody("12.34,-0.12")));
weatherService.stubFor(get(urlEqualTo("/forecast/12.34,-0.12"))
.willReturn(aResponse().withBody("{\"currently\":{\"windSpeed\":3.55}}")));

在这个用例里,测试人员希望在 Web 界面上看到已经计算好的地理坐标和相应的预报结果下拉列表。这样,在切换地理坐标时可以直接得到相应的预报结果,而不需要再次发出请求。

Traffic Parrot 已经包含了这个功能,所以可以直接使用。

其它工具

如果你想在选定一个工具之前先挑挑拣拣,那么你真的有很多选择。除了 Wiremock 之外,我所知道的还有超过 40 个这种类型的工具,它们使用不同的语言编写,运行在不同的平台上,提供各种不同的功能。
这里例举几个:

  • Mountebank (可用于 Java、Python、C#、Clojure 等语言平台)
  • VCR (在 Ruby 和 Ruby on Rails 平台很受欢迎)
  • Stubby4j (基于 Java,提供了另一种设计 API 的方式)

不过基于 Java 的 WireMock 仍然是目前最流行的,它在 Github 上有 1400 多个关注和 40 多个贡献者

更多信息

作为这篇文章的补充阅读,Christopher Batey 在他的文章“构建容错性微服务的六大原则”里介绍了他使用 WireMock 的几种场景,包括连接丢失、连接变慢及其它场景。

下一步

关于作者

Wojciech Bulaty WB 软件咨询公司的高级软件工程师。他通过写作分享了很多他在敏捷、自动化、极限编程、TDD、BDD、结对编程和代码清理等方面的经验。WB 软件咨询公司为 British Sky Broadcasting、Lloyds Bank 以及很多初创公司做过咨询。他们最近发布了 Traffic Parrot ,一款用于创建虚拟服务的软件工具。

查看英文原文: How Java developers can use the Wiremock framework to simulate HTTP-based APIs

2016-11-29 15:049484
用户头像

发布了 322 篇内容, 共 148.8 次阅读, 收获喜欢 148 次。

关注

评论

发布
暂无评论
发现更多内容

【C语言】for 关键字

謓泽

11月月更

追求极致性能!RocketMQ消息通信详解

Java全栈架构师

Java 程序员 面试 RocketMQ 消息中间件

太强了!终于有人整理出了仿京东电商项目,再次开源了

钟奕礼

Java 编程 架构 项目 java程序员

欢迎来嫖!阿里P8高级技术专家携这份818页Java核心技术重磅来袭

钟奕礼

Java 编程 计算机 java程序员 java架构

java文件流

hello java

文件 程序 Java core 11月月更

Jmix 1.4 功能概览

世开 Coding

企业级低代码 Jmix 企业级应用程序开发

数字先锋| 教育资源乘云而来!46万城乡学子共享名师课堂

天翼云开发者社区

OpenHarmony开发之MQTT讲解

OpenHarmony开发者

OpenHarmony

旺链科技出席Hyperledger区块链技术峰会,分享数字乡村新业态

旺链科技

区块链 hyperledger 产业区块链 企业号十月PK榜

阿里云研发工程师刘睿:阿里云消息生态及最佳实践

云布道师

阿里云 云原生

4.0体验站|我对OceanBase 4.0社区版的体验与看法

OceanBase 数据库

高可用性集群软件就选Skybility HA!优势多多!

行云管家

高可用 双机热备

三面阿里java后台开发岗总结:进阿里必看这份究极面试文档

钟奕礼

Java 编程 java程序员 java面试 java架构

从零到一构建完整知识体系!阿里巴巴Java并发编程技术内幕全网首次公开

Java全栈架构师

源码 程序员 程序人生 Java并发 java面试

天翼云实时云渲染,助力打造世界VR产业大会云上之城

天翼云开发者社区

互联网公司网络堡垒机首选哪家品牌?有什么优势?

行云管家

互联网 网络安全 信息安全 堡垒机

【#HDC2022】HarmonyOS体验官活动正式开启,赶快投稿赢限量奖品吧!

HarmonyOS开发者

HarmonyOS

双11狂欢背后,火山引擎数智平台为品牌做了这件事

字节跳动数据平台

大数据 营销数字化 火山引擎

共享开源技术,共建开放生态丨平凯星辰余梦杰出席 2022 世界互联网大会开源论坛圆桌对话

PingCAP

开源

手慢无!清华大牛熬夜整理Spring微服务架构设计第2版文档,限时删

钟奕礼

Java 编程 架构 计算机 java程序员

面向对象基础

断墨寻径

面向对象 java;

Kubectl 命令总结

蜗牛也是牛

这次,听人大教授讲讲分布式数据库的多级一致性|TDSQL关键技术突破

腾讯云数据库

腾讯云 tdsql 腾讯云数据库 多级一致性 中国人民大学

视频服务HDR Vivid 还原色彩,让所见成“真”

HarmonyOS SDK

视频 HMS Core

想要设计一个良好的接口至少要考虑这14点!

程序员小毕

Java 编程 程序员 程序人生 java面试

Alibaba最新推出的Spring Cloud手册惨遭开源

小小怪下士

Java 程序员 阿里 SpringCloud

如何杜绝 spark history server ui 的未授权访问?

明哥的IT随笔

hadoop spark

LED透明屏焊接和插接安装以及三招提升稳定性

Dylan

LED LED显示屏 led显示屏厂家

Linux内存泄露案例分析和内存管理分享

京东科技开发者

负载均衡 集群 内存泄漏 Linux Cron 运维、

TiKV 源码阅读三部曲(三)写流程

PingCAP

源码阅读

让迁移不再开盲盒,让云也能省钱丨Hackathon 项目背后的故事第一期回顾

PingCAP

hackathon

使用WireMock模拟HTTP API服务_Java_Wojciech Bulaty_InfoQ精选文章