NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

使用 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:048920
用户头像

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

关注

评论

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

高效学习:如何制定有价值的学习目标

石云升

学习 28天写作

软件测试--cookie学习

测试人生路

软件测试

基于机器学习的逻辑回归模型

无誉

程序员的简历,要注意以下几点

田维常

简历

爱了爱了!带着问题深入学习Handler,吊打面试官系列!

欢喜学安卓

android 程序员 面试 移动开发

架构师 3 期 3 班 -week9- 总结

zbest

总结 week9

k8s 上运行我们的 springboot 服务之——springboot服务https请求

柠檬

Java k8s https Istio,

图说丨京东《技术重构社会供应链——未来科技趋势白皮书》

京东科技开发者

京东 智能供应链

2021面试脚本!夜读互联网Java开发27大专题,终入P7

比伯

Java 程序员 面试 计算机 技术宅

持币生息钱包APP系统开发|持币生息钱包软件开发

系统开发

Pulsar vs Kafka,CTO 如何抉择?

Apache Pulsar

大数据 pulsar 消息中间件

FFMpeg解码API以及在解码过程中存在的丢帧问题

wangwei1237

ffmpeg 视频解码

腾讯T3大佬亲自教你!一起刷完了这份1307页的安卓面试宝典吧,吐血整理

欢喜学安卓

android 程序员 面试 移动开发

全球城市ZoneId和UTC时间偏移量的最全对照表

YourBatman

GMT UTC ZoneId 时区

2020年终总结:回顾、反思、期待

书旅

年终总结

AQS之ReentrantReadWriteLock写锁

伯阳

读写锁 ReentrantReadWriteLock 多线程与高并发 lock

架构师 3 期 3 班 -week9- 作业

zbest

作业 week9

对微服务架构设计实践中若干问题的探讨

xcbeyond

微服务 28天写作

美团图数据库平台建设及业务实践

NebulaGraph

图数据库

Spring Boot 中集成ActiveMQ

武哥聊编程

Java springboot SpringBoot 2 28天写作 Activemq

mq消费幂等总结

A

MQ 幂等性 db

关注直播 解锁采集器新功能

滴滴云

运维 可观测性 直播技术 滴滴夜莺

深度模型的日志异常检测,还有谁不会?

华为云开发者联盟

运维 日志 网络 AIOPS 系统

[JetPack] LiveData源码解析

Changing Lin

android 技术 JetPack

Java 程序经验小结:避免使用终结方法

后台技术汇

28天写作

第一周

Richard

如何成为一名优秀的技术Leader?

架构精进之路

成长笔记 七日更 28天写作

数据结构与算法 - 哈夫曼树

ios 数据结构与算法

数字钱包系统软件开发|数字钱包APP开发

系统开发

Redis中哈希分布不均匀该怎么办

Java 数据库 编程 程序员 面试

还热乎的面经

书旅

百度 面试 面经

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