mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2026-05-11 04:04:34 +08:00
mod
This commit is contained in:
269
极客时间专栏/软件测试52讲/API自动化测试篇/22 | 从0到1:API测试怎么做?常用API测试工具简介.md
Normal file
269
极客时间专栏/软件测试52讲/API自动化测试篇/22 | 从0到1:API测试怎么做?常用API测试工具简介.md
Normal file
@@ -0,0 +1,269 @@
|
||||
<audio id="audio" title="22 | 从0到1:API测试怎么做?常用API测试工具简介" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/19/f6/1954902443429760cef761328cf6d4f6.mp3"></audio>
|
||||
|
||||
你好,我是茹炳晟,我今天分享的主题是“从0到1:API测试怎么做?常用API测试工具简介”。
|
||||
|
||||
在第11篇文章[《互联网产品的测试策略应该如何设计?》](https://time.geekbang.org/column/article/11462)中,我介绍过当今互联网产品的测试策略往往会采用菱形结构,即重量级 API 测试,轻量级 GUI 测试,轻量级单元测试,由此可见API测试在现今测试中的重要性不言而喻。
|
||||
|
||||
这篇文章是API自动化测试系列的第一篇文章,我会先为你打好API测试的基础。所以,我会先从0到1设计一个API测试用例,通过这个测试用例,你可以体会到最基本的API测试是如何进行的,并介绍几款常用的API测试工具。
|
||||
|
||||
## API测试的基本步骤
|
||||
|
||||
通常来讲,无论采用什么API测试工具,API测试的基本步骤主要包括以下三大步骤:
|
||||
|
||||
<li>
|
||||
准备测试数据(这是可选步骤,不一定所有API测试都需要这一步);
|
||||
</li>
|
||||
<li>
|
||||
通过API测试工具,发起对被测API的request;
|
||||
</li>
|
||||
<li>
|
||||
验证返回结果的response。
|
||||
</li>
|
||||
|
||||
对API的测试往往是使用API测试工具,比如常见的命令行工具cURL、图形界面工具Postman或者SoapUI、API性能测试的JMeter等。
|
||||
|
||||
为了让你更好地理解API测试具体是怎么做的,并掌握常见API测试工具的使用,我会以基于主流Spring Boot框架开发的简单Restful API为例,分别介绍如何使用cURL和Postman对其进行最基本的功能测试,目的是让你对API测试有一个基本的感性认识。
|
||||
|
||||
## 基于Spring Boot构建的API
|
||||
|
||||
因为基于Spring Boot从0到1构建一个API,并不是本文的重点,为了不影响你对文章主要内容的把握,我直接采用了一个预先开发好的Account API为例展开讲解。你可以从[https://github.com/SpectoLabs/spring-cloud-contract-blog](https://github.com/SpectoLabs/spring-cloud-contract-blog)下载完整的代码。
|
||||
|
||||
这个Account API的功能非常简单,就是基于你提供的ID值创建一个Account对象,并返回这个新创建Account对象。
|
||||
|
||||
比如,如果你的请求是“account/ID008”,那么返回的response就应该是“{“id”:“ID008”,“type”:“friends”,“email”:“robin@api.io”}”。
|
||||
|
||||
这个Account API的功能逻辑实现非常简单,图1和图2列出了主要的代码逻辑。
|
||||
|
||||
图1中,代码的第21行说明了API的endpoint以及对应的操作是GET方法,第22行明确说明了GET方法具体的业务逻辑是由accountService.getById()方法实现的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/22/6e/220ed0f17737529eee9c1284fba6d06e.png" alt="" />
|
||||
|
||||
图2中,代码的第8行实现了accountService.getById()方法,具体逻辑就是返回一个以传入ID为ID的Account对象。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/10/fb/10a26edf4c7681a3cfbb68654a1d3bfb.png" alt="" />
|
||||
|
||||
我推荐使用IntelliJ打开这个下载的项目,然后直接启动其中的account-service。启动成功后,account-service会运行在本地机器的8080端口。启动成功后的界面如图3所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c4/8e/c45ca4ea536e130374e3da4f8426718e.png" alt="" />
|
||||
|
||||
## 使用cURL命令行工具进行测试
|
||||
|
||||
首先,你需要下载安装cURL,然后就可以通过以下命令发起Account API的调用。调用结束后的界面如图4所示。
|
||||
|
||||
```
|
||||
curl -i -H "Accept: application/json" -X GET "http://127.0.0.1:8080/account/ID008"
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/82/52/82571f2ccf198739ed4162574a851f52.png" alt="" />
|
||||
|
||||
这行命令中参数的含义如下:
|
||||
|
||||
- 第一个参数“-i”,说明需要显示response的header信息;
|
||||
- 第二个参数“-H”,用于设定request中的header;
|
||||
- 第三个参数“-X”,用于指定执行的方法,这里使用了GET方法,其他常见的方法还有POST、PUT和DELETE等,如果不指定“-X”,那么默认的方法就是GET。
|
||||
- 最后“ [http://127.0.0.1:8080/account/ID008](http://127.0.0.1:8080/account/ID008) ”,指明了被测API的endpoint以及具体的ID值是“ID008”。
|
||||
|
||||
当使用cURL进行API测试时,常用参数还有两个:
|
||||
|
||||
- “-d”:用于设定http参数,http参数可以直接加在URL的query string,也可以用“-d”带入参数。参数之间可以用“&”串接,或使用多个“-d”。
|
||||
- “-b”:当需要传递cookie时,用于指定cookie文件的路径。
|
||||
|
||||
需要注意的是这些参数都是大小写敏感的。
|
||||
|
||||
了解了这几个最常用的参数后,我再来分析一些最常用的cURL命令以及使用的场景,包括Session的场景和Cookie的场景。
|
||||
|
||||
**第一,Session的场景**
|
||||
|
||||
如果后端工程师使用session记录使用者登入信息,那么后端通常会传一个 session ID给前端。之后,前端在发给后端的requests的header中就需要设置此session ID,后端便会以此session ID识别出前端是属于具体哪个session,此时cURL的命令行如下所示:
|
||||
|
||||
```
|
||||
curl -i -H "sessionid:XXXXXXXXXX" -X GET "http://XXX/api/demoAPI"
|
||||
|
||||
```
|
||||
|
||||
**第二,Cookie的场景**
|
||||
|
||||
如果是使用cookie,在认证成功后,后端会返回cookie给前端,前端可以把该cookie保存成为文件,当需要再次使用该cookie时,再用“-b cookie_File” 的方式在request中植入cookie即可正常使用。具体的cURL的命令行如下所示:
|
||||
|
||||
```
|
||||
// 将cookie保存为文件
|
||||
curl -i -X POST -d username=robin -d password=password123 -c ~/cookie.txt "http://XXX/auth"
|
||||
|
||||
// 载入cookie到request中
|
||||
curl -i -H "Accept:application/json" -X GET -b ~/cookie.txt "http://XXX/api/demoAPI"
|
||||
|
||||
```
|
||||
|
||||
最后,需要特别说明的是,cURL只能发起API调用,而其本身并不具备结果验证能力(结果验证由人完成),所以严格意义上说cURL并不属于测试工具的范畴。但是由于cURL足够轻量级,经常被很多开发人员和测试人员使用,所以我在这里做了简单的介绍。
|
||||
|
||||
接下来,我们再来看看如何使用目前主流的Postman完成API测试。
|
||||
|
||||
## 使用图形界面工具Postman进行测试
|
||||
|
||||
Postman是目前使用最广泛的Http请求模拟工具之一,常常被用于Web Service API的测试。
|
||||
|
||||
早期的Postman,是以Chrome浏览器的插件(plugin)形式存在的,最新版本的Postman已经是独立的应用了。我猜想是因为这个工具的应用日益广泛,所以才有了今天的独立版本。
|
||||
|
||||
你可以通过[官方网站](http://www.getpostman.com)下载对应于Mac、Windows和Linux操作系统的不同版本,截止文章写作完成时,最新的Mac版本是6.2.2。
|
||||
|
||||
接下来,我就会以Mac 6.2.2版本为例,跟你分享如何用Postman完成你的API测试。如果你使用浏览器的plugin版本,或者是基于其他操作系统的版本,这都没问题,基本的操作和步骤都是一样的。
|
||||
|
||||
具体的操作,主要包括:
|
||||
|
||||
<li>
|
||||
发起API调用;
|
||||
</li>
|
||||
<li>
|
||||
添加结果验证;
|
||||
</li>
|
||||
<li>
|
||||
保存测试用例;
|
||||
</li>
|
||||
<li>
|
||||
基于Postman的测试代码自动生成。
|
||||
</li>
|
||||
|
||||
**第一步,发起API调用**
|
||||
|
||||
我们的目标是对Account API做测试,所以这里你需要选择Postmant的“Request”模块。进入相应界面后,你需要按照图5的提示依次执行以下三步操作,发起Account API的调用。
|
||||
|
||||
<li>
|
||||
在endpoint输入框中输入“[http://127.0.0.1:8080/account/ID_008](http://127.0.0.1:8080/account/ID_008)”;
|
||||
</li>
|
||||
<li>
|
||||
选择“GET”方法;
|
||||
</li>
|
||||
<li>
|
||||
点击“Send”按钮发起API调用。
|
||||
</li>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d7/b1/d7bc64b03f6296ffabf79ce6c938e1b1.png" alt="" />
|
||||
|
||||
完成以上步骤后,界面如图6所示。我们看到返回的response默认以JSON文件的形式显示在下面的Body中。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/db/ce/db132983af623cee5835b01ecd37b5ce.png" alt="" />
|
||||
|
||||
这样就完成了一次Account API的调用,是不是非常简单。但问题是,这只是一个API调用,并没有对调用结果进行自动化验证。接下来,我们就加上结果验证的部分,一起看看会有什么效果。
|
||||
|
||||
**第二步,添加结果验证**
|
||||
|
||||
在Postman中添加结果验证也非常方便,假定我们在Account API测试过程中有以下四个验证点:
|
||||
|
||||
<li>
|
||||
请求的返回状态码(Status Code)应该是200;
|
||||
</li>
|
||||
<li>
|
||||
请求的响应时间应该小于200 ms;
|
||||
</li>
|
||||
<li>
|
||||
请求返回的response header中应该包含“Content-Type”参数;
|
||||
</li>
|
||||
<li>
|
||||
请求返回的response body中,“type”的值应该是“friends”;
|
||||
</li>
|
||||
|
||||
那么,接下来我们一起来看看如何使用Postman来添加这四个验证点。
|
||||
|
||||
为此,我们首先打开“Tests”界面,然后在右下角的“SNIPPETS”中依次点击:
|
||||
|
||||
<li>
|
||||
“Status code: Code is 200”
|
||||
</li>
|
||||
<li>
|
||||
“Response time is less than 200 ms”
|
||||
</li>
|
||||
<li>
|
||||
“Response headers:Content-Type header check”
|
||||
</li>
|
||||
<li>
|
||||
“Response body: JSON value check”
|
||||
</li>
|
||||
|
||||
完成以上操作后,“Tests”中会自动生成验证代码,接着只要按照具体的测试要求,对这些生成的代码进行一些小修改就可以了。
|
||||
|
||||
在这个例子中,你只需修改需要验证的JSON键值对即可,即代码的第15行。修改完成后我们可以再次点击“Send”按钮发起测试。测试通过的界面如图7所示,最下面的“Test Results”显示四个测试全部通过。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/69/67/6988a0f511d9742b49f0514f46e1a967.png" alt="" />
|
||||
|
||||
**第三步,保存测试用例**
|
||||
|
||||
测试通过后,我们往往希望可以把这个测试request保存下来,以方便后续使用,为此Postman提供了保存测试request的功能,并提供了Collection来分类管理保存多个测试request。
|
||||
|
||||
Collection是用来保存测试request的一个集合,Collection内部还可以建立目录结构以方便进一步的分类和管理。
|
||||
|
||||
这里我们点击“Save As”按钮,在弹出的对话框中可以建立Collection,并且可以命名测试request并将其保存到Collection中。
|
||||
|
||||
我建立了“API Test Demo”的Collection,并且将刚才的测试request命名为“AccountAPI”保存到这个Collection中。
|
||||
|
||||
以后再要使用这个测试request时,直接在Collection中打开它,即可使用。同时你如果申请注册了一个Postman账号,就可以很方便地在多个环境中共享这个Collection了。
|
||||
|
||||
**第四步,基于Postman的测试代码自动生成**
|
||||
|
||||
至此,你已经掌握了Postman最基本的使用方法,但还有一个问题没有解决。很多时候,你希望将你的测试request作为回归测试用例集成到CI/CD的流程中,这就要求可以通过命令行的方式执行你的测试。为了达到这个目的,目前有两种做法:
|
||||
|
||||
<li>**将Postman中的测试request用自动化的方式直接转换成API测试的代码。** 目前Postman已经支持这个功能了,可以将保存的测试request自动化转换成常见测试框架直接支持的代码,而且支持多语言。<br />
|
||||
比如,基于Java的“OK HTTP”和“Unirest”,基于Python的“http.client”和“Requests”,基于NodeJS的“Native”“Request”和“Unirest”,基于JavaScript的“JQuery AJAX”和“XHR”等等。你可以点击如图8所示的“Code”按钮进入代码生成界面。</li>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/52/41/523124687d3551e1da9bbe300b0faf41.png" alt="" />
|
||||
|
||||
1. **利用Newman工具直接执行Postman的Collection。** 你需要先将Postman中的Collection导出为JSON文件,然后执行以下命令行。
|
||||
|
||||
```
|
||||
newman run examples/sample-collection.json;
|
||||
|
||||
```
|
||||
|
||||
## 如何应对复杂场景的API测试?
|
||||
|
||||
我在前面分享的Restful API测试案例中,只涉及到了最基本的API的测试方法,而且测试场景也很比较简单(只是单个API的调用)。
|
||||
|
||||
但在实际项目中,除了这种单个API的测试场景外,还有很多复杂场景的API测试。所以,为了解决你在实际项目中可能会碰到的一些问题,我再和你聊聊目前一些常见的典型复杂场景,以及相应的测试思路和方法。
|
||||
|
||||
**测试场景一:被测业务操作是由多个API调用协作完成**
|
||||
|
||||
很多情况下,一个单一的前端操作可能会触发后端一系列的API调用,由于前端测试的相对不稳定性,或者由于性能测试的要求,你必须直接从后端通过模拟API的顺序调用来模拟测试过程。
|
||||
|
||||
这时,API的测试用例就不再是简单的单个API调用了,而是一系列API的调用,并且经常存在后一个API需要使用前一个API返回结果的情况,以及需要根据前一个API的返回结果决定后面应该调用哪个API的情况。
|
||||
|
||||
好在,**我们已经实现了API的调用和结果解析的代码化,这也就意味着我们可以很灵活地直接用代码来处理这些场景了。** 比如,通过代码将上个API调用返回的response中的某个值传递给下一个API,再比如根据上一个API的返回结果决定下一个应该调用哪个API等。
|
||||
|
||||
除此之外,**我们还需要迫切解决的一个问题是:如何才能高效地获取单个前端操作所触发的API调用序列。**
|
||||
|
||||
解决这个问题的核心思路是,通过网络监控的手段,捕获单个前端操作所触发的API调用序列。比如,通过类似于Fiddler之类的网络抓包工具,获取这个调用序列;又比如,目前很多互联网公司还在考虑基于用户行为日志,通过大数据手段来获取这个序列。
|
||||
|
||||
**测试场景二:API测试过程中的第三方依赖**
|
||||
|
||||
API之间是存在依赖关系的,比如你的被测对象是API A,但是API A的内部调用了API B,此时如果由于某种原因,API B在被测环境中处于不可用状态,那么API A的测试就会受到影响。
|
||||
|
||||
在单体架构下,通常只会在涉及到第三方API集成的场景中才会遇到这个问题,所以还不算严重。但是,在微服务架构下,API间相互耦合的依赖问题就会非常严重。
|
||||
|
||||
解决这个问题的核心思路是,启用Mock Server来代替真实的API。那么,Mock Server怎么才能真实有效地模拟被替代的API呢?这个问题,我会在分享《紧跟时代步伐:微服务模式下API测试要怎么做?》这个主题时,和你详细探讨。
|
||||
|
||||
**测试场景三:异步API的测试**
|
||||
|
||||
异步API是指,调用后会立即返回,但是实际任务并没有真正完成,而是需要稍后去查询或者回调(Callback)的API。
|
||||
|
||||
一直以来,异步API测试都是API测试中比较困难的部分。**在我看来,对异步API的测试主要分为两个部分:一是,测试异步调用是否成功,二是,测试异步调用的业务逻辑处理是否正确。**
|
||||
|
||||
- 异步调用是否成功,这个还比较简单,主要检查返回值和后台工作线程是否被创建两个方面就可以了。
|
||||
<li>但是,对异步调用业务逻辑的测试就比较复杂了,因为异步API通常发生在一些比较慢的操作上,比如数据库I/O、消息队列I/O等,此时测试往往需要去验证数据库中的值、消息队列中的值等,这就需要测试代码具有访问和操作数据库或者消息队列的能力。<br />
|
||||
在实际工程项目中,这些能力一般会在测试框架级别提供,也就是说要求API测试框架中包含对应的工具类去访问和操作数据库或者消息队列等。</li>
|
||||
|
||||
## 总结
|
||||
|
||||
通常情况下,无论你采用什么API测试工具,基本的测试步骤往往都是三步,即准备测试数据(并不是所有的API测试都需要这一步)、通过API测试工具发起对被测API的request、验证返回结果的response。
|
||||
|
||||
接下来,我通过一个简单的Restful API测试为例,和你分享了cURL和Postman这两个常用API测试工具的使用。
|
||||
|
||||
其中,cURL只具备发起API调用的功能,而不具备结果验证能力,所以严格地说它并不属于测试工具的范畴。Postman常常被用于Web Service API的测试具体的操作,测试流程主要包括:发起API调用、添加结果验证、保存测试用例、基于Postman的测试代码自动生成。
|
||||
|
||||
最后,为了帮你应对实际工程项目中复杂的API测试场景,我分享了被测业务操作是由多个API调用协作完成、API测试过程中的第三方依赖、异步API的测试,这三个复杂场景下的测试思路和方法。
|
||||
|
||||
## 思考题
|
||||
|
||||
单个API测试是比较简单的,但在实际项目中,往往存在按时序的API调用以及异步API调用,这类API你是如何测试的?遇到过什么难题,又是如何解决的?
|
||||
|
||||
感谢你的收听,欢迎给我留言讨论。
|
||||
|
||||
|
||||
170
极客时间专栏/软件测试52讲/API自动化测试篇/23 | 知其然知其所以然:聊聊API自动化测试框架的前世今生.md
Normal file
170
极客时间专栏/软件测试52讲/API自动化测试篇/23 | 知其然知其所以然:聊聊API自动化测试框架的前世今生.md
Normal file
@@ -0,0 +1,170 @@
|
||||
<audio id="audio" title="23 | 知其然知其所以然:聊聊API自动化测试框架的前世今生" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/0b/3a/0bbab498518d7d829fcf11bf28cd883a.mp3"></audio>
|
||||
|
||||
你好,我是茹炳晟,今天我和你分享的主题是“知其然知其所以然:聊聊API自动化测试框架的前世今生”。
|
||||
|
||||
在上一篇文章中,我以一个简单的Restful API为例,分别介绍了cURL和Postman的使用方法,相信你已经对API测试有个感性认识了。
|
||||
|
||||
但是,我们不能仅仅停留在感性认识的层面,还需要熟悉并掌握这些测试方法,完成相应的API测试工作。所以,也就有了我今天分享的主题,希望可以通过对API自动化测试框架发展的介绍,让你理解API测试是如何一步一步地发展成今天的样子,以“知其所以然”的方式加深你对API自动化测试的理解。
|
||||
|
||||
接下来,我将会遵循由简入繁的原则,为你介绍API测试框架,以发现问题然后解决问题的思路为主线,展开今天的分享。
|
||||
|
||||
## 早期的基于Postman的API测试
|
||||
|
||||
早期的API测试,往往都是通过类似Postman的工具完成的。但是,由于这类工具都是基于界面操作的,所以有以下两个问题亟待解决:
|
||||
|
||||
<li>
|
||||
当需要频繁执行大量的测试用例时,基于界面的API测试就显得有些笨拙;
|
||||
</li>
|
||||
<li>
|
||||
基于界面操作的测试难以与CI/CD流水线集成。
|
||||
</li>
|
||||
|
||||
所以,我们迫切需要一套可以基于命令行执行的API测试方案。这样,API测试可以直接通过命令行发起,与CI/CD流水线的整合也就方便得多了。
|
||||
|
||||
## 基于Postman和Newman的API测试
|
||||
|
||||
于是就出现了集成Postman和Newman的方案,然后再结合Jenkins就可以很方便地实现API测试与CI/CDl流水线的集成。Newman其实就是一个命令行工具,可以直接执行Postman导出的测试用例。
|
||||
|
||||
用Postman开发调试测试用例,完成后通过Newman执行,这个方案看似很完美。但是在实际工程实践中,测试场景除了简单调用单个API以外,还存在连续调用多个API的情况。
|
||||
|
||||
此时,往往会涉及到多个API调用时的数据传递问题,即下一个API调用的参数可能是上一个API调用返回结果中的某个值。另外,还会经常遇到的情况是,API调用前需要先执行一些特定的操作,比如准备测试数据等。
|
||||
|
||||
因此,对于需要连续调用多个API并且有参数传递的情况,Postman+Newman似乎就不再是理想的测试方案了。
|
||||
|
||||
## 基于代码的API测试
|
||||
|
||||
为了解决这个问题,于是就出现了基于代码的API测试框架。比较典型的是,基于Java的OkHttP和Unirest、基于Python的http.client和Requests、基于NodeJS的Native和Request等。
|
||||
|
||||
小型的互联网企业,往往会根据自己的业务需求,选用这些成熟的API测试框架。
|
||||
|
||||
但是,对于中大型的互联网企业,一般都会自己开发更适合自身业务上下文的API测试框架,比如eBay,我们为了实现代码化的API测试,开发了自己的HttpClient,后期为了使API测试的代码更简洁易懂,就基于Rest-Assured封装了全新的API测试框架。
|
||||
|
||||
这种根据公司业务上下文开发实现的API测试框架,在使用上有很多优点,而且灵活性也很好,主要体现在以下几个方面:
|
||||
|
||||
<li>
|
||||
可以灵活支持多个API的顺序调用,方便数据在多个API之间传递,即上一个API调用返回结果中的某个字段值可以作为后续API调用的输入参数;
|
||||
</li>
|
||||
<li>
|
||||
方便在API调用之前或者之后执行额外的任意操作,可以在调用前执行数据准备操作,可以在调用后执行现场清理工作等;
|
||||
</li>
|
||||
<li>
|
||||
可以很方便地支持数据驱动测试,这里的数据驱动测试概念和GUI测试中的数据驱动测试完全相同,也就是可以将测试数据和测试代码分离解耦;
|
||||
</li>
|
||||
<li>
|
||||
由于直接采用了代码实现,所以可以更灵活地处理测试验证的断言(Assert);
|
||||
</li>
|
||||
<li>
|
||||
原生支持命令行的测试执行方式,可以方便地和CI/CD工具做集成。
|
||||
</li>
|
||||
|
||||
这里我给出了一段伪代码示例,用于展示如何用代码实现一个简单的API测试。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/be/6f/be4e9d0cf1442e89831629f3f5727c6f.png" alt="" />
|
||||
|
||||
<li>
|
||||
代码的第1-12行,创建了CreateUserAPI类,其中包含了endpoint、操作方法PUT、InlineParam和Param的设置,并且构建了对应的request对象;
|
||||
</li>
|
||||
<li>
|
||||
代码的第14-19行,是测试的主体函数。这段函数的逻辑是这样的:
|
||||
<ul>
|
||||
- 首先,构建CreateUserAPI的对象;
|
||||
- 然后,用CreateUserAPI对象的buildRequest方法结合输入参数构建request对象;
|
||||
- 接着,通过request对象的request()方法发起了API调用;
|
||||
- 最后,验证response中的状态码是不是200。
|
||||
|
||||
在这段伪代码中,有以下几点需要你特别注意:
|
||||
|
||||
<li>
|
||||
代码中“CreateUserAPI的父类RestAPI”“_buildRequest()方法”“request()方法”“addInlineParam()方法”等,都是由API测试框架提供的。
|
||||
</li>
|
||||
<li>
|
||||
为了简化代码,这里并没有引入数据驱动的data provider。但在实际项目中,代码第14行的测试输入参数,往往来自于data provider,即由数据驱动的方式提供测试输入数据。
|
||||
</li>
|
||||
<li>
|
||||
由于测试过程完全由代码实现,所以可以很方便的在测试执行前后增加任意的额外步骤。比如,需要在CreateUser前增加数据创建的步骤时,只需要在代码第15行前直接添加就可以了。
|
||||
</li>
|
||||
<li>
|
||||
这里的例子只有一个API调用,当需要多个API顺序调用时,直接扩展testCreateUser方法即可,两个API之间的数据传递可以通过上一个API返回的response.XXXX完成。
|
||||
</li>
|
||||
|
||||
通过这段伪代码,我们可以看到,虽然基于代码的API测试灵活性很好,也可以很方便地和CI/CD集成,但是也引入了一些新的问题,比如:
|
||||
|
||||
- 对于单个API测试的场景,工作量相比Postman要大得多;
|
||||
- 对于单个API测试的场景,无法直接重用Postman里面已经积累的Collection。
|
||||
|
||||
在实际工程中,这两个问题非常重要,而且必须要解决。因为公司管理层肯定无法接受相同工作的工作量直线上升,同时原本已经完成的部分无法继续使用,所以自动化生成API测试代码的技术也就应运而生了。
|
||||
|
||||
## 自动生成API测试代码
|
||||
|
||||
自动生成API测试代码是指,基于Postman的Collection生成基于代码的API测试用例。
|
||||
|
||||
其实,在上一篇文章[《从0到1:API测试怎么做?常用API测试工具简介》](https://time.geekbang.org/column/article/13421)最后的部分,我已经提到过Postman工具本身已经支持将Collection转化成测试代码,但如果直接使用这个功能的话,还有两个问题需要解决:
|
||||
|
||||
<li>
|
||||
测试中的断言(assert)部分不会生成代码,也就是说测试代码的生成只支持发起request的部分,而不会自动生成测试验证点的代码;
|
||||
</li>
|
||||
<li>
|
||||
很多中大型互联网企业都是使用自己开发的API测试框架,那么测试代码的实现就会和自研API测试框架绑定在一起,显然Postman并不支持这类代码的自动生成。
|
||||
</li>
|
||||
|
||||
鉴于以上两点,理想的做法是自己实现一个代码生成工具,这个工具的输入是Postman中Collection的JSON文件,输出是基于自研API框架的测试代码,而且同时会把测试的断言一并转化为代码。
|
||||
|
||||
**这个小工具实现起来并不复杂,其本质就是解析Collection JSON文件的各个部分,然后根据自研API框架的代码模板实现变量替换。** 具体来讲,实现过程大致可以分为以下三步:
|
||||
|
||||
- 首先,根据自研API框架的代码结构建立一个带有变量占位符的模板文件;
|
||||
- 然后,通过JSON解析程序,按照Collection JSON文件的格式定义去提取header、method等信息;
|
||||
- 最后,用提取得到的具体值替换之前模板文件中的变量占位符,这样就得到了可执行的自研框架的API测试用例代码。
|
||||
|
||||
有了这个工具后,我建议你的工作模式(Working Model)可以转换成这样:
|
||||
|
||||
- 对于Postman中已经累积的Collection,全部由这个工具统一转换成基于代码的API测试用例;
|
||||
- 开发人员继续使用Postman执行基本的测试,并将所有测试用例保存成Collection,后续统一由工具转换成基于代码的API测试用例;
|
||||
- 对于复杂测试场景(比如,顺序调用多个API的测试),可以组装由工具转换得到的API测试用例代码,完成测试工作。
|
||||
|
||||
如图2所示,就是一个组装多个由工具转换得到的API测试用例代码的例子。其中,代码第3行的类“CreateUserAPI”和第10行的类“BindCreditCardAPI”的具体代码就可以通过工具转换得到。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8c/e9/8cc97f6e1459aac8aedbb14ab8bec4e9.png" alt="" />
|
||||
|
||||
至此,基于代码的API测试发展得算是比较成熟了,但在实际应用过程中还有一个痛点一直未被解决,那就是测试验证中的断言,也是我接下来要和你一起讨论的话题。
|
||||
|
||||
## Response结果发生变化时的自动识别
|
||||
|
||||
在实际的工程项目中,开发了大量的基于代码的API测试用例后,你会发现一个让人很纠结的问题:到底应该验证API返回结果中的哪些字段?
|
||||
|
||||
因为你不可能对返回结果中的每一个字段都写assert,通常情况下,你只会针对关注的几个字段写assert,而那些没写assert的字段也就无法被关注了。
|
||||
|
||||
但对API测试来说,有一个很重要的概念是后向兼容性(backward compatibility)。API的后向兼容性是指,发布的新API版本应该能够兼容老版本的API。
|
||||
|
||||
后向兼容性除了要求API的调用参数不能发生变化外,还要求不能删减或者修改返回的response中的字段。因为这些返回的response会被下游的代码使用,如果字段被删减、改名或者字段值发生了非预期的变化,那么下游的代码就可能因为无法找到原本的字段,或者因为字段值的变化而发生问题,从而破坏API的后向兼容性。
|
||||
|
||||
所以,我们迫切需要找到一个方法,既可以不对所有的response字段都去写assert,又可以监测到response的结构以及没有写assert的字段值的变化。
|
||||
|
||||
在这样的背景下,诞生了“Response结果变化时的自动识别”技术。也就是说,即使我们没有针对每个response字段都去写assert,我们仍然可以识别出哪些response字段发生了变化。
|
||||
|
||||
具体实现的思路是,在API测试框架里引入一个内建数据库,推荐采用非关系型数据库(比如MongoDB),然后用这个数据库记录每次调用的request和response的组合,当下次发送相同request时,API测试框架就会自动和上次的response做差异检测,对于有变化的字段给出告警。
|
||||
|
||||
你可能会说这种做法也有问题,因为有些字段的值每次API调用都是不同的,比如token值、session ID、时间戳等,这样每次的调用就都会有告警。
|
||||
|
||||
但是这个问题很好解决,现在的解决办法是通过规则配置设立一个“白名单列表”,把那些动态值的字段排除在外。
|
||||
|
||||
## 总结
|
||||
|
||||
为了让你可以更好地理解今天的API测试框架,我从其发展历程的角度进行了分析:
|
||||
|
||||
早期的基于Postman的API测试在面临频繁执行大量测试用例,以及与CI/CD流水线整合的问题时,显得心有余而力不足。为此,基于命令行的API测试实践,也就是Postman+Newman,具有很好的灵活性,解决了这两个问题。
|
||||
|
||||
但是,Postman+Newman的测试方案,只能适用于单个API调用的简单测试场景,对于连续调用多个API并涉及到参数传递问题时,这个方案就变得不那么理想和完美了。随后,API测试就过渡到了基于代码的API测试阶段。
|
||||
|
||||
一些小型企业,则往往会选择适合自己业务的成熟API测试框架。中大型的互联网企业,一般都会根据自己的业务上下文,在成熟API测试框架的基础上封装自己的API测试框架,提升测试效率和灵活性。
|
||||
|
||||
但是,不管是采用现成的还是自己去开发API测试框架,都会遇到测试用例开发效率低下,以及无法直接重用Postman中积累的Collection的问题,为此我分享了两个比较好用的方法,也就是:自动生成API测试代码和Response结果变化的自动识别,并给出了这两个方法的实现思路。
|
||||
|
||||
希望我分享的这些内容,可以帮你解决在实际测试项目中遇到的问题。
|
||||
|
||||
## 思考题
|
||||
|
||||
目前,基于代码的API测试框架已经比较成熟了,所以在此基础上又出现了基于配置文件的API测试框架,比如典型的HttpRunner,在此类API测试框架的支持下,测试用例本身往往就是纯粹的配置文件了。你是否有接触过这类API测试框架,对此又有什么看法呢?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
176
极客时间专栏/软件测试52讲/API自动化测试篇/24 | 紧跟时代步伐:微服务模式下API测试要怎么做?.md
Normal file
176
极客时间专栏/软件测试52讲/API自动化测试篇/24 | 紧跟时代步伐:微服务模式下API测试要怎么做?.md
Normal file
@@ -0,0 +1,176 @@
|
||||
<audio id="audio" title="24 | 紧跟时代步伐:微服务模式下API测试要怎么做?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/da/7d/dad5b58f460cb4ed695a96e6f890ad7d.mp3"></audio>
|
||||
|
||||
你好,我是茹炳晟,今天我分享的主题是“紧跟时代步伐:微服务模式下API测试要怎么做?”。
|
||||
|
||||
通过一个的Restful API实例,我介绍了cURL和Postman工具的基本用法,这样我们对API测试有了一个感性认识;在此基础上,我介绍了API自动化测试框架发展的来龙去脉,借此我们对API测试框架的理解又更深入了一层。
|
||||
|
||||
今天,我将更进一步,带你去了解当下最热门的技术领域的API测试,即微服务模式下的API测试。微服务架构下,API测试的最大挑战来自于庞大的测试用例数量,以及微服务之间的相互耦合。所以,我今天分享这个主题的目的就是,帮你理解这两个问题的本质,以及如何基于消费者契约的方法来应对这两个难题。
|
||||
|
||||
而为了掌握微服务模式下的API测试,你需要先了解微服务架构(Microservice Architecture)的特点、测试挑战;而要了解微服务架构,你又需要先了解一些单体架构(Monolithic Architecture)的知识。所以,今天的话题我将逐层展开,目的就是希望你可以真正理解,并快速掌握微服务模式下的API测试。
|
||||
|
||||
## 单体架构(Monolithic Architecture)
|
||||
|
||||
单体架构是早期的架构模式,并且存在了很长时间。单体架构是将所有的业务场景的表示层、业务逻辑层和数据访问层放在同一个工程中,最终经过编译、打包,并部署在服务器上。
|
||||
|
||||
比如,经典的J2EE工程,它就是将表示层的JSP、业务逻辑层的Service、Controller和数据访问层的DAO(Data Access Objects),打包成war文件,然后部署在Tomcat、Jetty或者其他Servlet容器中运行。
|
||||
|
||||
显然单体架构具有发布简单、方便调试、架构复杂性低等优点,所以长期以来一直被大量使用,并广泛应用于传统企业级软件。
|
||||
|
||||
但是,随着互联网产品的普及,应用所承载的流量越来越庞大,单体架构的问题也被逐渐暴露并不断放大,主要的问题有以下几点:
|
||||
|
||||
- **灵活性差**:无论是多小的修改,哪怕只修改了一行代码,也要打包发布整个应用。更糟的是,由于所有模块代码都在一起,所以每次编译打包都要花费很长时间。
|
||||
- **可扩展性差**:在高并发场景下,无法以模块为单位灵活扩展容量,不利于应用的横向扩展。
|
||||
- **稳定性差**:当单体应用中任何一个模块有问题时,都可能会造成应用整体的不可用,缺乏容错机制。
|
||||
- **可维护性差**:随着业务复杂性的提升,代码的复杂性也是直线上升,当业务规模比较庞大时,整体项目的可维护性会大打折扣。
|
||||
|
||||
正是因为面对互联网应用时,单体架构有这一系列无法逾越的鸿沟,所以催生了微服务架构。
|
||||
|
||||
其实,微服务架构也不是一蹴而就的,也经历了很长时间的演化发展,中间还经历了著名的SOA架构。但是这个由单体架构到SOA架构再到微服务架构的演进过程,并不是本文的重点,所以我就不再详细展开了,如果你感兴趣的话,可以自行去查阅一些相关资料。
|
||||
|
||||
## 微服务架构(Microservice Architecture)
|
||||
|
||||
微服务是一种架构风格。在微服务架构下,一个大型复杂软件系统不再由一个单体组成,而是由一系列相互独立的微服务组成。其中,各个微服务运行在自己的进程中,开发和部署都没有依赖。
|
||||
|
||||
不同服务之间通过一些轻量级交互机制进行通信,例如 RPC、HTTP 等,服务可独立扩展伸缩,每个服务定义了明确的边界,只需要关注并很好地完成一件任务就可以了,不同的服务可以根据业务需求实现的便利性而采用不同的编程语言来实现,由独立的团队来维护。
|
||||
|
||||
图1就很形象地展示了单体架构和微服务架构之间的差异。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/69/9d/6968c125be2de960b48d8df35315159d.png" alt="" />
|
||||
|
||||
微服务架构具有以下特点:
|
||||
|
||||
- 每个服务运行在其独立的进程中,开发采用的技术栈也是独立的;
|
||||
- 服务间采用轻量级通信机制进行沟通,通常是基于HTTP协议的RESTful API;
|
||||
- 每个服务都围绕着具体的业务进行构建,并且能够被独立开发、独立部署、独立发布;
|
||||
- 对运维提出了非常高的要求,促进了CI/CD的发展与落地。
|
||||
|
||||
## 微服务架构下的测试挑战
|
||||
|
||||
由于微服务架构下,一个应用是由很多相互独立的微服务组成,每个微服务都会对外暴露接口,同时这些微服务之间存在级联调用关系,也就是说一个微服务通常还会去调用其他微服务,鉴于以上特点,微服务架构下的测试挑战主要来自于以下两个方面:
|
||||
|
||||
<li>
|
||||
过于庞大的测试用例数量;
|
||||
</li>
|
||||
<li>
|
||||
微服务之间的耦合关系。
|
||||
</li>
|
||||
|
||||
接下来,我会针对这两项挑战分别展开,包括它们从何而来,以及如何应对这些挑战,最终完成测试。
|
||||
|
||||
**第一,过于庞大的测试用例数量**
|
||||
|
||||
在传统的API测试中,我们的测试策略通常是:
|
||||
|
||||
- 根据被测API输入参数的各种组合调用API,并验证相关结果的正确性;
|
||||
- 衡量上述测试过程的代码覆盖率;
|
||||
- 根据代码覆盖率进一步找出遗漏的测试用例;
|
||||
- 以代码覆盖率达标作为API测试成功完成的标志。
|
||||
|
||||
这也是单体架构时代主流的API测试策略。为了让你更好地理解这种测试策略,我来举一个实际的例子。
|
||||
|
||||
假设我们采用单体架构开发了一个系统,这个系统对外提供了3个Restful API接口,那么我们的测试策略应该是:
|
||||
|
||||
- 针对这3个API接口,分别基于边界值和等价类方法设计测试用例并执行;
|
||||
- 在测试执行过程中,启用代码覆盖率统计;
|
||||
- 假设测试完成后代码行覆盖率是80%,那么我们就需要找到那些还没有被执行到的20%的代码行。比如图2中代码的第242行就是没有被执行到,分析代码逻辑后发现,我们需要构造“expected!=actual”才能覆盖这个未能执行的代码行;
|
||||
- 最终我们要保证代码覆盖率达到既定的要求,比如行覆盖率达到100%,完成API测试。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ab/e2/ab5e06ccd5d4e3925fe86024b54162e2.png" alt="" />
|
||||
|
||||
**而当我们采用微服务架构时,原本的单体应用会被拆分成多个独立模块,也就是很多个独立的service,原本单体应用的全局功能将会由这些拆分得到的API共同协作完成。**
|
||||
|
||||
比如,对于上面这个例子,没有微服务化之前,一共有3个API接口,假定现在采用微服务架构,该系统被拆分成了10个独立的service,如果每个service平均对外暴露3个API接口,那么总共需要测试的API接口数量就多达30个。
|
||||
|
||||
如果我还按照传统的API测试策略来测试这些API,那么测试用例的数量就会非常多,过多的测试用例往往就需要耗费大量的测试执行时间和资源。
|
||||
|
||||
但是,在互联网模式下,产品发布的周期往往是以“天”甚至是以“小时”为单位的,留给测试的执行时间非常有限,所以微服务化后API测试用例数量的显著增长就对测试发起了巨大的挑战。
|
||||
|
||||
这时,我们迫切需要找到一种既能保证API质量,又能减少测试用例数量的测试策略,这也就是我接下来要分享的**基于消费者契约的API测试**。
|
||||
|
||||
**第二,微服务之间的耦合关系**
|
||||
|
||||
微服务化后,服务与服务间的依赖也可能会给测试带来不小的挑战。
|
||||
|
||||
如图3所示,假定我们的被测对象是Service T,但是Service T的内部又调用了Service X和Service Y。此时,如果Service X和Service Y由于各种原因处于不可用的状态,那么此时就无法对Service T进行完整的测试。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/79/9b/79f5990fd864012cb1c7cd87c8e5079b.png" alt="" />
|
||||
|
||||
我们迫切需要一种方法可以将Service T的测试与Service X和Service Y解耦。
|
||||
|
||||
解耦的方式通常就是实现Mock Service来代替被依赖的真实Service。实现这个Mock Service的关键点就是要能够模拟真实Service的Request和Response。当我介绍完基于消费者契约的API测试后,你会发现这个问题也就迎刃而解了。
|
||||
|
||||
## 基于消费者契约的API测试
|
||||
|
||||
那到底什么是基于消费者契约的API测试呢?直接从概念的角度解释,会有些难以理解。所以我打算换个方法来帮助你从本质上真正理解什么是基于消费者契约的API测试。接下来,就跟着我的思路走吧。
|
||||
|
||||
首先,我们来看图4,假设图4中的Service A、Service B和Service T是微服务拆分后的三个Service,其中Service T是被测试对象,进一步假定Service T的消费者(也就是使用者)一共有两个,分别是Service A和Service B。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4b/6f/4b60dcc958636c29ae8edd5e52db216f.png" alt="" />
|
||||
|
||||
按照传统的API测试策略,当我们需要测试Service T时,需要找到所有可能的参数组合依次对Service T进行调用,同时结合Service T的代码覆盖率进一步补充遗漏的测试用例。
|
||||
|
||||
这种思路本身没有任何问题,但是测试用例的数量会非常多。那我们就需要思考,如何既能保证Service T的质量,又不需要覆盖全部可能的测试用例。
|
||||
|
||||
静下心来想一下,你会发现Service T的使用者是确定的,只有Service A和Service B,如果可以把Service A和Service B对Service T所有可能的调用方式都测试到,那么就一定可以保证Service T的质量。即使存在某些Service T的其他调用方式有出错的可能性,那也不会影响整个系统的功能,因为这个系统中并没有其他Service会以这种可能出错的方式来调用Service T。
|
||||
|
||||
现在,问题就转化成了如何找到Service A和Service B对Service T所有可能的调用方式。如果能够找出这样的调用集合,并以此作为Service T的测试用例,那么只要这些测试用例100%通过,Service T的质量也就不在话下了。
|
||||
|
||||
从本质上来讲,这样的测试用例集合其实就是,Service T可以对外提供的服务的契约,所以我们把这个测试用例的集合称为“基于消费者契约的API测试”。
|
||||
|
||||
那么接下来,我们要解决的问题就是:如何才能找到Service A和Service B对Service T的所有可能调用了。其实这也很简单,在逻辑结构上,我们只要在Service T前放置一个代理,所有进出Service T的Request和Response都会经过这个代理,并被记录成JSON文件,也就构成了Service T的契约。
|
||||
|
||||
如图5所示,就是这个过程的原理了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1e/86/1e9c9cb78a02e3675e612da46e2c1b86.png" alt="" />
|
||||
|
||||
在实际项目中,我们不可能在每个Service前去放置这样一个代理。但是,微服务架构中往往会存在一个叫作API Gateway的组件,用于记录所有API之间相互调用关系的日志,我们可以通过解析API Gateway的日志分析得到每个Service的契约。
|
||||
|
||||
至此,我们已经清楚地知道了如何获取Service的契约,并由此来构成Service的契约测试用例。接下来,就是如何解决微服务之间耦合关系带来的问题了。
|
||||
|
||||
## 微服务测试的依赖解耦和Mock Service
|
||||
|
||||
在前面的内容中,我说过一句话:实现Mock Service的关键,就是要能够模拟被替代Service的Request和Response。
|
||||
|
||||
此时我们已经拿到了契约,契约的本质就是Request和Response的组合,具体的表现形式往往是JSON文件,此时我们就可以用该契约的JSON文件作为Mock Service的依据,也就是在收到什么Request的时候应该回复什么Response。
|
||||
|
||||
下面的图6就解释了这一关系,当用Service X的契约启动Mock Service X后,原本真实的Service X将被Mock Service X替代,也就解耦了服务之间的依赖,图6中的Service Y也是一样的道理。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e1/01/e18bf590c818e5dc26e85a5e07eb1401.png" alt="" />
|
||||
|
||||
## 代码实例
|
||||
|
||||
自此,我已经讲完了基于消费者契约的API测试的原理,你是否已经都真正理解并掌握了呢?
|
||||
|
||||
由于这部分内容的理论知识比较多,为了帮你更好地理解这些概念,我找了一个基于Spring Cloud Contract的实际代码的示例演示契约文件格式、消费者契约测试以及微服务之间解耦,希望可以帮到你。
|
||||
|
||||
具体的实例代码,你可以从[https://github.com/SpectoLabs/spring-cloud-contract-blog](https://github.com/SpectoLabs/spring-cloud-contract-blog)下载,详细的代码解读可以参考[https://specto.io/blog/2016/11/16/spring-cloud-contract/](https://specto.io/blog/2016/11/16/spring-cloud-contract/)。
|
||||
|
||||
这个实例代码,基于Spring Boot实现了两个微服务:订阅服务(subscription-service)和账户服务(account-service),其中订阅服务会调用账户服务。这个实例基于Spring Cloud Contract,所以契约是通过Groovy语言描述的,也就是说实例中会通过Groovy语言描述的账户服务契约来模拟真实的账户服务。
|
||||
|
||||
这个实例的逻辑关系如图7所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/db/fa/db1b2b058505fef62805f9077e1982fa.png" alt="" />
|
||||
|
||||
## 总结
|
||||
|
||||
单体架构,具有灵活性差、可扩展性差、可维护性差等局限性,所以有了微服务架构。
|
||||
|
||||
微服务架构的本身的特点,比如微服务数量多,各个微服务之间的相互调用,决定了不能继续采用传统API测试的策略。
|
||||
|
||||
为了既能保证API质量,又能减少测试用例数量,于是有了基于消费者契约的API测试。基于消费者契约的API测试的核心思想是:只测试那些真正被实际使用到的API调用,如果没有被使用到的,就不去测试。
|
||||
|
||||
基于消费者契约的测试方法,由于收集到了完整的契约,所以基于契约的Mock Service完美地解决了API之间相互依赖耦合的问题。
|
||||
|
||||
这已经是API自动化测试系列的最后一篇文章了,短短的三篇文章可能让你感觉意犹未尽,也可能感觉并没有涵盖到你在实际工程项目中遇到的API测试的所有问题,但是一个专栏区区几十篇文章的确无法面面俱到。
|
||||
|
||||
我通过这个专栏更想达到的目的是:讲清楚某一技术的来龙去脉及其应用场景,但是很多具体操作级别、代码实现级别的内容,还是需要你在实践中不断积累。
|
||||
|
||||
所以,如果你还有关于API测试的其他问题,非常欢迎你给我留言讨论,让我们一起来碰撞出思想火花吧!
|
||||
|
||||
## 思考题
|
||||
|
||||
基于消费者契约的API测试中,对于那些新开发的API,或者加了新功能的API,由于之前都没有实际的消费者,所以你无法通过API Gateway方法得到契约。对于这种情况,你会采用什么方法来解决呢?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user