This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,139 @@
<audio id="audio" title="01 | 基础:跳出细节看全局,接口测试到底是在做什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ea/7e/ea965cf717561a641dbe7addf6d0867e.mp3"></audio>
你好,我是陈磊。
今天开始,我们就来聊一聊接口测试的那些事儿,这是我们专栏的第一节课,在讲解如何做好接口测试之前,我想先给你讲讲它的必要性,再讲讲什么是接口、什么是接口测试。
## 接口测试为什么重要?
我相信你一定听说过这样一句话:“测试要尽早介入,测试进行得越早,软件开发的成本就越低,就越能更好地保证软件质量。”
但是如何尽早地进入测试,作为软件测试工程师的你,是不是也没办法说得清楚呢?其实上面那句话中的“测试”,所指的并不是测试工程师这个人,而是指包含了单元测试、接口测试、界面测试等一系列质量保障活动的测试工作。
说到单元测试、接口测试和界面测试,你是不是马上就想到了“测试金字塔模型”呢?
<img src="https://static001.geekbang.org/resource/image/54/26/54babe3ca1b8baf147d6b83486f86b26.jpg" alt="">
在这个金字塔模型中,界面测试、接口测试和单元测试,每一个阶段所占面积的大小,代表了它们在测试过程中的投入和工作量占比。
你可以看到,单元测试在测试过程中,占据了绝大部分的比重,这表示单元测试需要你投入更多的时间和人力成本。但是,单元测试并非测试工程师的本职工作,它属于开发工程师的工作范筹。说到这你可能会问了:“如果开发工程师从来不写单元测试怎么办?”毕竟大部分开发人员都不爱写测试。
其实,我也会问自己这个问题。不可否认,开发工程师不只很少写单元测试,更很少写出好的单元测试代码,很多时候,工期的压力让他们放弃了单元测试。但是,一个产品的交付质量更多时候却是由测试工程师来保障的,面对这一实际现状,我们又该怎么办好呢?
我们聪明的测试工程师提供了两种解决手段一种是用一些智能化框架补充单元测试工作如果你对智能化单元测试感兴趣可以参考我在2019年TiD上的演讲内容“[自动的自动化测试智能化一站式API测试服务](https://mp.weixin.qq.com/s?__biz=MzUxOTg3NzA4Mg==&amp;mid=2247485068&amp;idx=4&amp;sn=dcabc199c0f9ba03319ecfdbe07a0b62&amp;chksm=f9f3be39ce84372f78d00a691559cdedef1e54355d12243efc5821c74f39def0d78164bd9296&amp;token=2056996305&amp;lang=zh_CN#rd)”);另外一种,就是加大我们自己主导的接口测试的工作投入比重,来弥补单元测试的不足,这样,上面那个金字塔模型就会逐渐演变成菱形模型。
<img src="https://static001.geekbang.org/resource/image/af/60/afec8e77c9fc22d448f9b2885d278b60.jpg" alt="">
那之所以出现从“金字塔模型”到“棱形模型”这种变化,并不是有人刻意提高测试工程师在整个交付流程中的地位,这其实是随着工作的不断进行,逐渐形成的结果。
在质量保障过程中我们的测试工程师会不断增大接口测试的测试深度和测试广度往下逐渐覆盖一些公共接口的单元测试内容往上则逐渐覆盖应该由UI层保障的业务逻辑测试这么做的主要目的就是为了更好地完成质量保障工作交付一个可靠的、高质量的项目。
所以从接口测试这一环节开始测试工程师就变成了质量保障工作的主要推动者接口测试也变得愈发重要。那它有什么好处和优越性呢我觉得可以从下面这3个角度来看
1. 接口测试更容易和其他自动化系统相结合;
1. 相对于界面测试,接口测试可以更早开始,也可以测试一些界面测试无法测试的范围,因此它使“测试更早的投入”这句话变成现实;
1. 接口测试还可以保障系统的鲁棒性,使得被测系统更健壮。
现在,我相信你已经意识到接口测试在质量保障中的重要地位了,那么,你知道究竟什么是接口吗?接口测试又在测些什么呢?我们又为什么要做接口测试呢?
下面我就逐一把这些讲解给你。
## 接口是什么?
如果你想要知道接口测试在测什么,首先就要知道接口是什么。
在这里我不想告诉你书本上是怎么定义接口的,从那些晦涩的语言中,你可能读几次都不能真正理解它的含义,我准备用一个实际生活中的例子,来告诉你接口究竟是什么。
我相信你肯定去过麦当劳,那每次在你去麦当劳吃东西时,你是否细心观察过它为你准备订单商品的过程呢?
如果你的订单上有一个汉堡,工作人员会先找到汉堡的原材料如面包片、肉饼和生菜等,按照规定步骤,将这些原材料组合成一个汉堡,然后送给你;如果你的订单上有一份薯条,那么工作人员会进入另外一个工作流程,先找到薯条原材料和炸薯条的锅,把薯条炸好后,送到你面前。
那么在上面的例子中,汉堡以及薯条的原材料就是接口中必要的条件入参,也就是接口的特定输入;制作汉堡或烹饪薯条的过程,就是接口内部的处理逻辑;送到你面前的汉堡和薯条,就是接口的处理结果和特定输出,也就是返回参数。
**所以我们可以看到,接口就是有特定输入和特定输出的一套逻辑处理单元,而它不用知道自身的内部实现逻辑,这也可以叫做接口的黑盒处理逻辑。**
而从上面的例子你也可以看到,由于服务对象不同,接口又可以分为两种,一种是系统或服务的内部接口,一种是外部依赖接口。
<li>
<h3>内部接口</h3>
</li>
简单来说,内部接口就是系统内部调用的接口。
在上面麦当劳的例子中,内部接口有两个:
- 汉堡订单。服务员在接到订单后,输入汉堡的原材料,将汉堡做好后,放到后厨和前台之间的一个中间储存柜里,作为输出,为下一个中间储物柜接口提供输入参数。
- 中间储物柜。服务员从中间储物柜拿出汉堡,这就是这个内部接口的特定输入,最后送到你面前的汉堡,就是这个内部接口的特定输出。
那么在软件系统中,内部接口是怎么一回事呢?
其实,你在网上购物时,要先登录系统,然后将商品加入购物车,再接下来支付订单。那么,从添加商品到购物车,再到支付订单,这一长串的流程之间,就是通过系统内部接口来完成的。
<li>
<h3>外部接口</h3>
</li>
刚刚说了内部接口,那什么是外部接口呢?其实它是相对于内部接口而存在的一个概念,上面你在麦当劳点餐的场景就是一个外部接口,它又可以分为两部分:
- 出订单前,你的点餐过程。这个外部接口特定的输入是你在点餐时,告诉服务员你想点什么,这也是你输出给麦当劳的参数。
- 出订单后,服务员送餐的过程。它的特定的输出是服务员把汉堡送给你,这也是麦当劳返回给你的处理结果参数。
在系统上,外部接口又是怎么回事呢?
你在购物后点击付款时,页面会跳转到支付系统,等你完成支付流程后,再跳转回订单页,在这样的流程中,都会涉及系统对外的接口,还有,比如说付款工程的支付接口、配送过程的物流接口等等。
**现在我来总结一下接口的本质,它其实就是一种契约,遵循这样一种形式:在开发前期,我们约定接口会接收什么数据;在处理完成后,它又会返回什么数据。**
如果调用方和被调用方都遵从了这种契约,那么就可以达到共同开发的目的,开发完成后,联调完成系统逻辑的前期预期,提高研发效能。
## 什么是接口测试?
**还是以麦当劳的汉堡为例,接口测试,其实就是要验证制作汉堡的过程是否正确。**这里所说的“正确”其实有两方面的意思:
- 一方面,是要验证输入了汉堡的原材料,经过制作汉堡的处理流程,最后交付给你的是一个汉堡;
- 另外一方面,是要验证在输入的汉堡原材料不对或者不全的情况下,经过制作汉堡的处理流程后,不能给你交付一个汉堡。
你一定要注意,**这两方面的验证是都要进行的,对于一个测试工程师来说,这两种流程都是正向流程。只有理解了这个思维,你才能把自己从客户思维里拉出来,形成测试工程师思维。**
我相信你在工作中已经接触过各式各样的接口了比如说HTTP协议的接口、RESTful格式的接口、WebService的接口、RPC协议的接口等。其实无论是哪一种形式的接口它们都是通过某一种传输协议在Client端和Server端之间来完成数据传递的。
- 假如你现在测试的是Web端的极客时间那么Client端就是浏览器Server端就是Web服务那么浏览器和Web服务之间就是通过HTTP协议传输的
- 如果你测试的是移动端的极客时间那么Client端就是你的设备上安装的极客时间应用Server端就是RESTful格式的接口服务那么极客时间的应用和RESTful格式的接口服务就是通过JSON格式的数据来传递的。
**看到这我想你也能理解了接口测试其实就是模拟调用方比如Client端通过接口通信来检测被测接口的正确性和容错性。**
但是请你放心我现在所说的模拟调用方并不是让你开发一个浏览器或者极客时间的手机应用而是让你模拟这些客户端上的前端逻辑调用Server端提供的接口你完全可以借助一些工具或代码来完成这项工作。
这些相关的工具或代码技巧,也就是我设计这个专栏的主要思路。但我不想把这些工具一次都罗列给你,让你马上就失去兴趣,这是由于两个原因:
- 当你看到一个好几页的工具列表或者技术列表式时,你可能会觉得,自己需要翻越无数个高峰才能学会接口测试;
- 另一方面我也觉得,在接口测试中,工具或代码并不是它的核心内容,接口测试思维才是你应该重点关注的问题。
所以,我会在做一些工作或者任务的过程中,把这些工具介绍给你,让你知道哪些工具能够解决哪些问题,用什么样的代码可以解决什么样的问题。我也更希望你知道,工具和代码并不是相互排斥的,而是相互依存和相互辅助的。
其实,接口测试和你以前最熟悉的业务测试一样,都是关注输入和预期是否一致,尤其是输入数据中有一些非法输入的时候,接口的处理和逻辑控制是否合理,这些都是通过返回值来判定的。
还有一些小概率逻辑的处理也是我们设计输入的关注重点,比如一些代码中的异常情况,我们也要想办法,通过输入参数来触发这种逻辑分支,通过返回值来判定对应接口内部实现的处理逻辑是否合理、是否健壮。
这样看来,接口测试对于你来说,也不是一个全新的工作内容,但它还有自身的特别之处的,比如说:
- 在测试手段上,接口测试算是技术驱动和业务驱动双管齐下的工作(界面测试却是业务驱动为主的工作),因此,你需要借助一定的工具来完成它。这个工具既有可能是成熟的工具,也有可能是你自己写的代码,因此,测试技术会在接口测试阶段,变得和业务知识一样重要。
- 在工作范围上,接口测试影响的范围会更广一点,它会覆盖一部分单元测试的内容,也会覆盖一部分业务测试的内容,但是,无论是哪一部分的内容被它侵占,相对应部分的工作投入其实都减少了。
## 总结
今天我们一起聊了聊接口测试,有人说,从吃的开始聊起就很容易打开谈话的僵局,所以我们从麦当劳的汉堡开始,讲述了接口为什么重要、接口是什么以及接口测试在测些什么。我还讲了接口测试和业务测试的区别和联系,那就是“相互依存,不可分割”。
虽然我今天洋洋洒洒给你讲了很多内容,你也有可能记住的并不多,但我希望下面这三点你一定要烂熟于心:
1. 接口测试是通过设计输入和预期输出来完成测试验证的,你之前掌握的测试用例设计方法等测试基本功,在这里还是有用武之地的;
1. 接口测试是一个技术知识和业务知识相结合的工作,可以更好地提升你自己的技术实力,让那些说我们是“点工”的人早早闭嘴;
1. 接口测试也是功能测试,要说有和界面测试不同的地方,仅仅是和我们交互的,不再是开发工程师设计的界面,而是测试工具或者代码。
在很多人眼里,接口测试是技术,业务测试是业务,但它们其实是不可分割的。所以在这个专栏中,我会通过先介绍方法再引入工具,到最后用代码引入封装框架,一步一步教你完成接口测试。
## 思考题
我们今天的课程到这里就结束了,现在我想请你思考一下,你还能举出其它实际的例子,证明接口测试是质量保障环境中,必不可少的一个测试阶段吗?
我是陈磊,欢迎你在留言区留言分享你的观点,如果这篇文章让你有新的启发,也欢迎你把文章分享给你的朋友,我们一起来探讨和学习。

View File

@@ -0,0 +1,190 @@
<audio id="audio" title="02 | 方法论:没有任何文档,怎么才能快速了解接口的信息?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/24/6c/24d0b367fbca4a856dfd538b1281fe6c.mp3"></audio>
你好,我是陈磊。
我相信在学习完上一节课后你已经明白了接口测试是在测什么我们为什么需要做接口测试。那么当你面对一个接口测试任务的时候你知道该如何开始吗其实任何事情从0到1都是一个门槛你只要跨过这个门槛后面就会一马平川。今天我就来告诉你如何开始接口测试让你面对一个项目不再束手无策也不再面露难色。
说起接口测试,我想你并不陌生。作为一名测试工程师,尤其是做了多年业务测试的测试工程师,在开始接触接口测试时,无论开发工程师是否提供了接口文档,我相信你都会对下面几种场景似曾相识:
1. 开发工程师提交测试的项目附带着一个几十页的Word文档里面是一行一行的访问地址和路由面对这样的Word文档不知道如何开始验证
1. 开发工程师在即时通讯工具上,甩给你有好几页的这么一个传输消息,里面有各种嵌套的参数,你不知道这些参数都是干什么用的;
1. 开发工程师口头告诉你需要测试的接口地址,然后就什么都没再多说,你问了他几句话后,他就借口说自己忙,不再理你,而你看到那个又长、又复杂的地址,束手无策。
难道,面对这些状况,测试工程师就没办法自己分析接口,完成测试吗?我现在告诉你,当然不是。
接下来,我就带你一起看看,一个理想的提测项目是什么样的,在实际工作中,绝大部分的提测项目又是什么样的,然后我们一起看看,如何一步一步解决一个不理想的提测项目。
## 一个理想的提测项目
一个理想的提测项目,在提测的过程中应该既包含前期参与的产品需求、原型设计,这些是由产品经理来提供的;也包含后端接口文档、代码单元测试脚本,这些是由开发工程师提供来的。它们都是你开展测试的必要输入内容,具体有这些作用:
- 产品需求。它描述了系统的业务逻辑,通过这个文档,你才能知道怎么来设计测试用例;
- 原型设计。它会更加直观地告诉你系统的使用逻辑,这对测试用例的设计、对系统的前期认知都是有辅助作用的。
- 接口文档。它详细地描述了后端接口的访问方式和参数说明,使用这个输入项才能开展接口测试用例的设计、测试脚本的准备和测试数据的构建。
- 单元测试脚本。它是保障提测质量的必要环节,是研发工程师自测的一个有效手段,可以保障提测项目的提测质量。
这些内容不限制SUTSystem Under Test被测系统的类型SUT既可以是一个手机App也可以是一个Web服务甚至还可以是一个微服务接口。
所以,对于接口测试阶段来说,一个非常理想的接口测试,就是从完美的接口文档开始的。开发工程师在设计和开发接口的过程中,就在不断维护和更新接口文档,这其中包含了每一个接口的访问方式、访问路由、输入参数含义、返回参数含义,以及一个完整的例子。
这种接口文档可能是以Word文档形式存在也有可能是以类似Swagger这种工具形式存在。说到Swagger它是我推荐给你的一个接口文档的存在形式是一个从代码生成的、以Web服务形式存在的接口文档它可以伴随代码的变更同步变化这就减少了很多开发工程师和测试工程师之间的沟通成本。
由Swagger生成的接口文档的例子如下图所示从图中你可以看到它对接口的访问方式、访问路由、参数都有着详细的描述。
<img src="https://static001.geekbang.org/resource/image/de/64/de22757455c405cd60f5490801c8ef64.jpg" alt="">
这样,当你拿到接口文档时,就可以快速使用各种工具或者代码来完成你的单个接口测试任务了。与此同时,你还可以通过一些参数设计、参数上下文传递,来完成接口的流程测试。
## 理想的情况很难发生
上面我说的是很理想的情况,现实却往往并不总如人意,我相信你也肯定遇见过下面这些情况:
- 根据产品经理的一句话需求,开发工程师便开始任意发挥,“所见系统即需求”的情况普遍存在,这就更别说后续的单元测试和接口文档了;
- 开发工程师从来不写单元测试脚本,提测项目质量无法保障,接口文档更无从谈起,你不知道如何开始完成接口测试;
- 你在拿到提测项目后从部署测试环境到开始测试一直都是摸黑前进由于接口测试没有充分的输入条件只能从UI层开始测试结果导致交付质量大打折扣。
就像墨菲定律说的那样:可能发生的事情必将会发生。所以,上面我列举的接口测试难以推行的一些常见情况,都是你在工作中会碰见的问题。
那么,如果一个项目没有接口文档,我们就无法开始接口测试了吗?当然不是。
测试工程师的工作本质上是一个由表及里的工作,如果你还是每次工作都处在和终端用户使用行为几乎一致的流程上,那么只能说明你还不算一名合格的测试工程师。
其实,无论开发工程师给我们的输入项是否包含了接口文档,我们都可以通过一些技术手段和工作方法,完成接口测试必要的输入项接口文档的创建。
那么这样的工作如何开始呢?下面我就来和你一起完成这样一项任务,教你如何开始第一个接口测试。
## 开始第一个接口测试
在拿到一个SUT环境的时候你首先就要进行接口测试这是因为单元测试不是由测试工程师来完成的而是由开发工程师编写、并由持续集成系统自动完成执行的。
如果开发工程师没有给我们任何有价值的文档,那么要开始接口测试,你可以通过工具辅助、分析问题、询问解惑这三个步骤来完成。
<img src="https://static001.geekbang.org/resource/image/52/0c/52483652c86dc2ce7f3459a1d773c30c.jpg" alt="">
具体的工作模式如上图所示:
1. 借助一些工具的辅助来完成接口分析;
1. 通过工具截获一些接口信息;
1. 通过分析接口的访问方式、参数等信息整理出一些问题,和研发工程师沟通这些问题,将一些不知道的参数含义、参数取值范围等问题问清楚。
通过这三步的循环你就可以完成对SUT系统接口信息的完善和维护最终得到一份完整的、接口测试需要的输入—接口文档。
下面我会结合一个案例,带你看看这三步具体该如何进行。
### 工具辅助
当你第一次拿到一个被测项目无论它是一个App服务还是一个Web服务你都可以通过一些HTTP代理完成接口分析这里我推荐你使用Fiddler这款工具。
>
<p>**注意:**<br>
Fiddler既支持Windows操作系统也支持MacOS操作系统但是在MacOs上的版本并不好用这是因为Fiddler使用了.Net开发。如果你是一个MacOS深度用户那么我推荐给你两款工具一款是Charles另外一款是mitmproxy。其中Charles是商业软件mitmproxy是开源软件但是Charles使用起来更简单mitmproxy的使用则稍微复杂一些你可以依据自己的喜好来选择。</p>
简单来说Fiddler[在这里下载](https://www.telerik.com/fiddler)是一个HTTP的调试代理也就是一个HTTP协议的抓包工具运行时会在本地建立一个代理服务默认地址为127.0.0.1:8888。当浏览器和被访问的服务之间发生交互的时候Request请求和Response响应都会经由Fiddler代理这样就可以截获下全部的访问信息流了。
在接下来的内容中我会带你使用Fiddler来完成接口的分析任务**但是我并不打算手把手的把Fiddler这个工具的使用细节讲述给你因为除了Fiddler其实还有很多工具可以完成对应的任务每一种工具又都有其具体的使用方法这里我真正想告诉你的是通过具体的方法来解决问题的思维。**
### 分析问题
在解决问题前,我们首先要分析问题,这也是我们开始接口测试的第二步。
下面我们一起来使用Fiddler分析一下极客时间Web端首页极客时间Web端首页地址https://time.geekbang.org
首先打开Fiddler然后启动浏览器访问极客时间Web端首页我们可以看到Fiddler截获了很多消息找到刚刚输入的https://time.geekbang.org如下图所示在右侧上方Inspectors的标签页下你就可以看到Request请求的内容和Response请求的内容了。
<img src="https://static001.geekbang.org/resource/image/59/bd/599426c3540b73b0bb673dda2ae0ebbd.jpg" alt="">
为方便你查看我将Request的消息体复制出来如下
```
POST https://time.geekbang.org/serv/v1/column/topList HTTP/1.1
Host: time.geekbang.org
Connection: keep-alive
Content-Length: 0
Accept: application/json, text/plain, /
Origin: https://time.geekbang.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Referer: https://time.geekbang.org/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _ga=GA1.2.2063652238.1573441551; _gid=GA1.2.1275017383.1573441551; Hm_lvt_022f847c4e3acd44d4a2481d9187f1e6=1573441551; GRID=b0a2570-01c8b13-90b002b-568dc07; MEIQIA_TRACK_ID=1TS7HZW4C6OWaPSu5VGIj7uN4pM; MEIQIA_VISIT_ID=1TS7HWSYajeZyS29IqNNyvW9cyY; SERVERID=1fa1f330efedec1559b3abbcb6e30f50|1573441580|1573441549; _gat=1; Hm_lpvt_022f847c4e3acd44d4a2481d9187f1e6=1573441790
```
从这段消息体中我们可以获知它的访问方式是POST访问的URI是“[https://time.geekbang.org/serv/v1/column/topList](https://time.geekbang.org/serv/v1/column/topList) ”。这里面的具体属性内容,你可以自行查看,但是,我希望你重点关注如下这几个属性:
- HOST它表示指定访问的服务器域名
- Connection的值为keep-alive这表示需要持久连接
- Accept它表示客户端可以接受的内容类型为application/json, text/plain, **/**
- User-Agent它说明请求是从什么浏览器发出去的
- Sec-Fetch-Site和Sec-Fetch-Mode它们是JS中对跨域的一些设置
- Accept-Encoding设置为gzip、deflate、br这表示可以支持的Web服务器返回内容压缩编码类型
- Accept-Language它表示接受的语言。
这其中的Cookie的内容是你需要特别非常关心的因为Cookie中传递的参数很多都是用来确认用户身份、鉴定角色权限等需要的参数。这样通过上面的分析你就可以自己绘制如下的表格这里我也给出了一张表格。
<img src="https://static001.geekbang.org/resource/image/08/68/08453bfd82584f4fce9c1c16140d0468.jpg" alt="">
在这个表格中被标注为白色背景的部分是这次访问的基本信息被标注为黄色背景的部分是访问的头信息同时也是我们已知的内容被标注为红色背景的部分就是Cookie信息是我们未知的内容。同时你可以看到本次消息访问的body是空的是没有内容的。
接下来我们再看看这个请求的Response信息由于返回的消息比较长我在这里就不贴出来了但是通过下面这个图你可以看出这次返回的主体是一个很长的JSON这里面包含了各个专栏或者课程的信息。
<img src="https://static001.geekbang.org/resource/image/6b/76/6b177a4d543c1799875ca3417f9ada76.jpg" alt="">
这些返回值包含了很多参数,你也需要关注这些参数,因为很多时候,一个接口的返回值会是另外一个接口的入参,也就是我常说的串联业务逻辑上下文的参数。
现在在你绘制的接口信息表中还有一些未知的Cookies内容就如我前面说的Cookie内容是完成接口测试必须要模拟并传递的一些信息因此我们必须要尽可能完善它使它成为接口测试的必要输入条件之一。
这样,拿着这张接口信息表,我们就进入了第三步,询问解惑。
### 询问解惑
对于本次访问的Cookies的参数从参数语义上来说我们无法知道这些参数是用来干什么的他又起到了什么作用作为测试人员我们也无法只靠自己知道每一项具体的含义、表示的内容以及参数的作用。
因此,我们需要拿着上面的那张表格,找到对应的开发工程师,去问清楚表格中标红部分的参数。
针对每一个参数,你都要从下面的几点详细询问,并保证你已经真的理解了这些内容。那么,都询问些什么呢?我认为主要有三点。
1. **参数的含义以及来源**。你要搞清楚每一个参数的含义也就是这个参数对应的实际自然语言的名字通过记录每一个参数的中文语义也会让更你容易记住这个函数是干什么的。同时你也要知道这个参数的赋值是从哪里来的是从其他页面的返回值中得到的还是JS生成的如果是其他页面或者接口返回的那么是哪一个接口返回的哪个字段这样当你开始做接口测试的时候你就知道去哪里拿到这个参数的赋值了。如果是另一个接口的返回字段那么你还需要维护一份返回该参数接口的接口信息文档以便于自己下一次创建对应的参数如果不可以创建那么你就要知道这个参数的生成规则也要知道如何手动构造它。
1. **参数的作用域**。参数的作用域指的是这个参数在这个接口中是做什么用的,它在哪一个访问周期里是一直存在的,它是否导致了业务逻辑分支等。比如说,这个参数是用来验证用户权限吗?它的验证算法是什么?之所以要搞清楚这些内容,是为了你在做接口测试的时候,可以设计更小的参数组合来覆盖更多的业务逻辑,这是测试用例去除冗余的一个很好的方法。
1. **返回值的含义**。针对上面一大串的返回JSON你要搞清楚在返回值中每一个JSON的Key所对应的含义这样当你需要和这个接口产生交互的时候就可以快速地拿到对应参数的含义完成业务逻辑上下文的参数串联了。
总地来说Request的全部参数和Response的全部参数对于接口测试来说都是必要的输入项因此我们有必要花费很多精力完善并且留存它们。
OK到现在你已经借助工具、通过分析问题明确了未知参数也通过询问解决了未知参数的中文含义、作用域以及对应返回参数的中文含义现在即使面对没有接口文档的提测项目你也能收集明确的、足够的信息了。
那么接下来,你就可以利用这些信息,完成业务逻辑的接口测试了。
## 多个接口串行分析
在质量保障过程中测试的主要任务是保障SUT的业务逻辑正确性而单一接口的测试却很难完成一个业务逻辑所以在大部分的测试场景中我们都需要串行多个接口才能完成一个完整的业务逻辑。
然而,即使我们按照上述三个步骤完成了全部单个接口的分析,也并不能马上开始进行接口测试。这是因为,一个测试的业务逻辑是由多个接口的串行完成的,而多个接口的串行逻辑是由业务逻辑规定的,因此,多个接口之间并不是随意组合的,而是按照业务逻辑、通过数据传递来完成的。
这其实就和拼图游戏一样,我们有一堆拼图碎片,很多拼图碎片都可以连接到一起,并不会有明显的不适合,但是,依据拼图的最终图形,这些拼图碎片就是不能放到一起。你要想把拼图完成,就不仅要考虑各个拼图碎片是不是可以链接到一起,还要考虑这些碎片放到一起后是不是对原来图形的正确拼接。
那么,你前面整理好的、各个单一接口的信息表,就是拼图游戏里的一个拼图碎片,业务逻辑就是拼图组成的最终图形,而其中的参数,就是拼图碎片的缺口和每一个碎片上的图形。
所以,要想使用接口测试完成业务逻辑,你就要制作一个流程中所有接口的接口信息表,同时,还要理清每一个流程的数据流程,数据流程驱动了业务流处理,这样,才能开始业务逻辑的接口测试。
关于怎么完成一个业务逻辑的接口测试,我会在下一节课中详细的解释给你。
## 总结
好了,今天的内容就到这里了,不知道你是不是已经掌握了分析一个接口并整理接口信息表的方法呢?现在我来总结一下。
在今天的课程中,我带你一起分析了一个理想的提测项目都包含哪些要素,但是,这种乌托邦式的提测项目在现实中却很难遇见。所以,我给你推荐了“三步走”的方法,你可以通过工具辅助、分析问题和询问解惑这三步来建立接口信息表,建立和维护自己的接口知识库。我也通过拼图游戏的例子,告诉你接口测试的业务逻辑验证思路。
随着被测系统的不断迭代,当你不断地按照这个步骤积累起自己的接口知识库时,你就会拥有自己负责的业务线的业务知识积累,变成这条被测业务线的业务专家;同时你也会拥有每一个接口的输入和输出全部参数、每一个业务逻辑的数据流、每一个驱动业务分支逻辑的参数条件,你也会成为接口测试的技术专家,在你自己的工作中变成一个不可或缺的质量保障者。
在一个团队中,有很多时候,测试工程师和开发工程师之间就是一个矛盾的共同体,既相互依存又不可分割,在团队中缺一不可,要想摆脱“测试非技术岗位”的帽子,就需要测试人员有自己的技术功底和技术素养。
无论你在工作中,遇到的是能很好相互支持的开发工程师,还是不好合作的开发工程师,你都要保持自己的技术能力,尽最大努力完成自己能够完成的所有事情,只有这样,你才能提高自己在团队中的话语权。
## 思考题
最后我想请你来思考一下你能用本节课讲述的Fiddler还有接口测试分析方法完成一个你自己项目的老旧接口的分析吗
我是陈磊,欢迎你在留言区留言分享你的观点,如果这篇文章让你有新的启发,也欢迎你把文章分享给你的朋友,我们一起沟通探讨。

View File

@@ -0,0 +1,107 @@
<audio id="audio" title="03 | 思维方式:用一个案例彻底理解接口测试的关键逻辑" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a1/2b/a1e3e5e90012b07edc08d205ceba8e2b.mp3"></audio>
你好,我是陈磊。
在前面的课程中,我们聊到了如何开始一个接口测试,我相信你一定掌握了整个过程的推进方法,这包括如何分析一个不理想的提测项目的接口,并在自己的能力范围内完善和维护接口文档,最终设计一个流程化接口测试用例。
你还记得这其中的关键点吗?其实就是:
1. 工具辅助。借助一些工具的辅助来完成接口分析。
1. 分析问题。通过分析接口的访问方式、参数等信息整理出要解决问题。
1. 询问解惑。针对问题和研发工程师进行沟通,把一些不知道的参数含义、参数取值范围等问题沟通清楚。
那么,这些都准备好后,你又如何通过一个实际方法落地接口测试呢?这里面就涉及到怎么做单接口的接口测试,怎么完成业务逻辑接口测试,以及用什么手段来完成接口测试等问题。接下来我会为你详细解答这些问题。
今天,我会带你一起利用[Postman](https://www.getpostman.com/)这款工具来测一个系统。
简单来说Postman就是一个HTTP协议客户端工具。但它只是我们完成这次任务的手段接口测试用例的设计和实现过程才是我今天想告诉你的重点内容所以我在这里不会给你讲它的详细使用方法而是会花更多时间告诉你怎么利用接口测试的思维方式来使用它。你也不用担心今天我们这节课涉及的Postman的功能都很简单不会因为你没有基础而显得晦涩难懂。
## 明确被测系统
有了被测系统我们才能开始聊接口测试,但是,目前网络上可以看到的系统例如极客时间的手机应用、百度网站等并不适合做接口测试的讲解,这是因为我们需要知道接口的每一个参数,以及一些接口的处理逻辑。
所以,我给你准备了我自己专门制作的一个小型系统**Battle**,它是一个理想的被测系统,你可以在[GitHub](https://github.com/crisschan/Battle)中下载它的详细系统代码。
我先来简单介绍一下它它是一个类似回合制的游戏通过接口测试的方法和服务器发生交互模拟两个角色进行决斗最后可以知道到底是谁赢了。详细的说明和代码都在GitHub上你可以自行查看。除了GitHub上你可以看到的单个接口的说明以外还有一个就是业务流程这个系统的业务流程是**进入系统后,选择武器,然后和你选择的敌人决斗。**
## 开始接口测试
在开始业务逻辑接口测试之前你要先通过接口测试的方法测试每一个接口都是正确的既要保证单接口的正确性也要保证接口的业务逻辑正确性这里所说的“正确”指的是“正确接受合法Request入参正确拒绝非法Request入参”。
### 单接口的测试
单接口测试的重点,其实就是保证该接口的正确性和健壮性,也就是说,你既要保证这个接口可以按照需求,正确处理传入的参数,给出正确的返回;也可以按照需求,正确的拒绝传入非正确的参数,给出正确的拒绝性返回。
现在我就带你一起使用Postman来检测单接口的测试。
首先打开Postman你可以看到它的UI结构很简单为了可以检测首页接口你需要设定HTTP的访问方式为GETURL是[http://127.0.0.1:12356/](http://127.0.0.1:12356/)点击发送按钮后就可以在工具下面的Body部分看到接口返回的说明性文本内容了这个内容是“please input your username(your english name) and password(your english name)”。<br>
<img src="https://static001.geekbang.org/resource/image/ac/86/ace3d078b5459228043dd7eecd664786.png" alt="">
对于一个GET请求的接口我们在上面已经完成了单个接口的测试工作。那么接下来我们就要检测第二个接口登录接口它的访问方式是POST参数是username和password这两个参数均不可以为空也不可以超过10个字符如果username和password这两个字符串相同会登录成功并返回后续的说明性文本否则就会正确拒绝登录。所以在这一步我们会多检查一项Request的参数设计用边界值方法设计的参数如下图所示<br>
<img src="https://static001.geekbang.org/resource/image/3f/5d/3ff05cc8a18353e991b376449d34cc5d.jpg" alt="">
在获取了参数后下面你就要借助Postman这个工具选择Post访问方式输入登录接口的URL在Request的Body中输入username=criss&amp;password=criss的参数然后点击发送接下来你就会在Response的Body中对应返回内容。如下图所示<br>
<img src="https://static001.geekbang.org/resource/image/47/5a/471dacd98d5ee7f5e714ec42aa4d925a.png" alt="">
按照上面的方法,依次完成剩余两个接口的测试用例设计和测试用执行过程后,你就完成了单个接口的测试工作。
然而完成了这一步,只能算是把接口测试工作完成了一半,另外一半就是要按系统的业务逻辑来完成“正确的流程可以完成处理,不正确的流程可以正确拒绝处理”这个验证。
### 业务流程接口测试
业务流程接口测试,主要是保障通过多个接口的串联操作可以完成原来需求中提出的业务逻辑,这也是它主要关注的内容。
在前面,我已经告诉过你这个系统的主要业务逻辑:“进入系统后,选择武器,然后和你选择的敌人决斗。”
依据上面这种业务逻辑描述,还不能完成业务流程的接口测试,我们需要对其做进一步的分析和细化。依据这个业务需求,至少有下面这几个业务流程:
- 正确登录系统后,选择武器,与敌人决斗,杀死了敌人;
- 正确登录系统后,选择武器,与敌人决斗,被敌人杀死;
- 正确登录系统后,选择武器,与敌人决斗,两个人同归于尽;
- 正确登录系统,选择武器,没有选择敌人,自尽而死;
- 正确登录系统,选择一个未提供的武器编号,选择一个敌人,自尽而死;
- 正确登录系统,选择武器,选择一个未出战的敌人(不在返回提示列表中),自尽而死。
那么针对这些业务流程,我们设计的参数如下:<br>
<img src="https://static001.geekbang.org/resource/image/e5/a4/e50eb34d177c6c22573a4f13235783a4.jpg" alt="">
接着你就可以利用Postman将过程参数手动传递给下一步接口这样你就可以建立一个业务流程的接口测试并最终完成测试了。Postman也提供了参数化的方法如果你对此感兴趣也可以自行学习。
继续按照上面的流程用Postman将上述其它五个流程完成你就完成自己的接口测试了。如下图<br>
<img src="https://static001.geekbang.org/resource/image/ed/a1/ed82fd824750154617eef7bbe80e08a1.png" alt="">
现在,你已经有五个业务逻辑接口测试用例了,但是通过观察上面的业务流测试,你会不会感觉与你自己平时手动写的业务测试相比,好像少了点什么?
没错,它和业务测试的测试用例相比,确实少了很多异常状况,比如正确登录、正拒绝登录、正确登陆选择的装备参数是字符串等等,这一系的业务流中的反向用例都没有进行验证。
这也是接口测试和业务测试在设计测试和执行测试过程中的差异点,在接口测试中,我们通过单个接口测试完成了全部异常状态的覆盖;而在业务流程中,我们更需要关心业务流和数据流的关系,并不需要再过度关心如何用业务流的方法覆盖更多的代码逻辑异常,这也是分层测试中为什么在单元测试和界面测试之间要加入一层接口测试的主要原因之一。
通过单接口测试,可以更加接近于单元测试;通过业务流的接口测试,可以更加接近于界面所承载的交互中的业务流验证,这也是为什么现在很多人在提倡将测试模型由原来的金字塔形往菱形转变的依据之一了。
**而完成了这一系列流程,其实你也就掌握了接口测试的思维:先从单个接口的测试开始,保障单个接口的正确性和健壮性,然后通过单个接口的测试完成多个接口的业务逻辑串联,站在业务逻辑的角度完成业务逻辑的正确性检测。**
## 你的接口测试也可以和持续集成结合到一起
通过Postman这个工具完成从单接口测试用例的设计到业务逻辑接口测试用例的设计你就已经掌握了接口测试的思维以及具体的实现方法。但是到目前为止你还处在手动测试的阶段虽然已经和以前基于界面的业务测经有了很大区别但是距离自动化的接口测试还有一定的差距。不过你不用担心因为这个差距仅仅是一个工具的距离。
我相信你一定听说过持续集成在持续集成中有一个很重要的环节就是要持续测试通过持续集成平台调取自动化测试完成质量保障工作。现在你已经有了Postman已经完成了基于Postman的接口测试脚本那么如何将其赋能给持续集成平台呢
这里我们要借助Newman这款工具它就是shell下的Postman我们将Postman的业务逻辑接口测试脚本导出后push到本地的Git仓库中持续集成平台就可以通过pull对应的接口测试脚本然后通过Newman执行这样就可以完成持续集成平台的赋能了。
在这里我只提供给你一个思路具体的完成方式你可以通过学习持续集成平台Jenkins和Newman运行Postman脚本完成对应的内容。
## 总结
我相信今天的内容你一定可以挪为己用了,如果你和我一起完成了这些操作,同时在课后,你也弥补了我在课上跳过去没有讲到的一些接口测试脚本,那么我相信你现在肯定被成功的喜悦所围绕了。
走到这一步你已经掌握了接口测试的思维在这种思维的指导之下用什么技术手段或者工具去完成接口测试也就显得没那么重要了这也是为什么我并没有将Postman这个工具一步一步教你怎么用的原因因为你既可以选择我推荐给你的Postman也可以找到一个你自己喜欢的工具或技术方式完成接口测试。
接口测试的执行方式、设计思维都和业务测试不完全一致,它们既有交集又有差异。交集部分是它们都会涉及到业务逻辑测试,但是接口测试更加关注有数据流驱动的业务流程,而不再着眼于代码异常、代码边界等,这些边界问题在接口测试过程中已经由单接口测试完成了。
接口测试在单接口测试的设计思维上也更加贴近于代码的单元测试但它还是站在Client端的角度来完成测试而接口测试的业务逻辑测试更加靠近手工业务测试但却更加聚焦于业务逻辑本身不再将一些非法业务异常放到该部分进行测试。
## 思考题
今天我并没有将全部测试用例都用Postman完成那么你自己完成了吗如果你已经完成了那么你能用Newman驱动执行单接口测试脚本和业务逻辑测试脚本了吗如果单接口测试中登录测试用例有一条执行失败了你可以告诉我是哪一个测试用例吗
我是陈磊,欢迎你在留言区留言分享你的观点,如果这篇文章让你有新的启发,也欢迎你把文章分享给你的朋友,我们一起探讨和学习。

View File

@@ -0,0 +1,73 @@
<audio id="audio" title="开篇词 | 把接口测试这件小事做深、做透" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3d/f6/3dde5fcd8e1d3eb14e44b5c379b2eff6.mp3"></audio>
你好,我是陈磊,欢迎来到我的接口测试课。
我曾经在京东中台工作,经历了京东虚拟平台质量团队从业务驱动到技术驱动的转型过程,我也是整个过程的推动者之一。
我对接口测试的认知,是我在从测试工程师做到测试架构师这个过程中逐渐形成的,因此,我想先和你讲讲我的从业经历,跟你分享一下我和接口测试的缘分。
## 接口测试思维,给我带来技术与能力的跃升
我在2009年硕士毕业后就成为了一名软件测试工程师。刚入行时我一直在做功能测试主要的工作内容就是设计测试用例然后手动执行。后来在工作中开始人工回放大量的测试用例我每天被各种项目的测试用例淹没在电脑前使用最多的软件就是Excel。**大量重复性的工作让我逐渐失去了目标,那时我认为,测试就是不断地人工点点点的操作,不是一个纯技术的工作。**
后来我被自动化测试的浪潮拍醒开始用Selenium自娱自乐地写自动化测试脚本。一开始我还是乐在其中的刚接手的项目写好自动化测试脚本后再次迭代时我只要运行一下测试脚本等着脚本自己运行结束就可以了。
但随着项目不断迭代我的Selenium脚本越来越难以应付我的测试任务了。最开始我还可以通过它来“偷懒”到后来它反而变成了我测试工作中的负担。不维护它我心里觉得不甘继续维护它又平白给自己增加了更多额外任务。那时界面自动化测试的一些问题我都遇见了例如项目页面变更频繁导致测试脚本维护成本特别高、页面重构导致脚本大面积失效等诸如此类的问题导致我加班时间越来越长只能用996的工作节奏才能勉强跟得上项目进度。
那时好像也没有极客时间这么好的学习应用我把碎片时间主要用在了刷技术类微博上。记得有一次偶然的机会我在一篇微博文章上看到了Postman这个做接口测试的小工具当时它的功能还没有现在这么强大但我也被它方便、易学、易用的特点吸引了。
我开始把Postman应用到我的工作中从接口测试开始完成我的测试任务并逐渐积累了很多测试脚本。在使用Postman开始接口测试后我逐渐放弃了Selenium的界面自动化测试也摆脱了加班维护界面自动化测试脚本的困境同时依靠它强大的功能也提升了整个项目的测试工作速度因此在很长一段时间里我的工作都很轻松每天都能准时下班。
**Postman这款工具也让我重新思考了测试工作我开始逐渐体会到测试工作也是一项技术驱动的工作测试工程师也是一个技术岗位。**
接着在一次工作中由于被测系统的接口不是HTTP协议我这才发现Postman在非HTTP协议的接口测试上已经没有办法发挥任何作用了于是我开始自己写接口测试脚本并慢慢将它封装成自己的测试框架。我做的测试框架从我的第一份工作伴随我到现在已经经历了无数次的迭代我现在依然还在维护它。
后来,我在京东中台担任测试架构师,主要负责中台的微服务接口测试,以及提高质量效能等工作,我的工作目标就是让机器做完接口自动化测试中费时、费力的事情,这包括测试脚本的开发、测试数据的准备、测试执行以及测试结果收集等待等一系列工作。
我和几个小伙伴也一起开发了自动的自动化接口测试平台AAT我也是AAT自动化脚本生成算法的主要设计者之一并在各种技术峰会上对它的关键算法做过详细介绍。关于它的一些主要思路我在我们团队一起撰写的《京东质量团队转型实践从测试到测试开发的蜕变》一书中也有介绍如果你对它感兴趣可以去看看这本书。
在京东工作期间,我通过引入算法完成了测试框架的一次完美升级,主要实现了框架自动的编写测试脚本,给出推荐的测试入参数据等特性。**但我深知,这些高级功能的实现都离不开我在之前所有工作中形成的接口测试思维,它仅仅是在一些特别的方向上变得更加先进和智能而已。**
伴随着我的成长,我的接口测试框架也在不断变得完善,就像武侠小说里那种人剑合一的感觉一样,我的技术和我在共同成长。在这个过程中,我完成了从一个具体的测试代码到一个框架设计的思维转变,拥有了平台设计的思维,并通过不断尝试和探索,完成了智能化测试框架的设计和开发。
从使用工具完成接口测试,到自己写代码完成接口测试,然后慢慢封装自己的框架,最后走到让测试框架更智能的技术路线上,这一路我走了十几年,走过不少弯路,也趟过不少坑。
在这个过程中我最深的感触就是:无论你在工作中参与了一个多么智能的测试平台的设计与开发,还是引入了一个多么强大的自动化测试框架,**你首先都要会用最原始的方式完成这件事情,这才是万变不离的宗。在这个基础上,如果你掌握了接口测试思维,那你不只可以快速掌握某一种测试工具来解决问题,更可以打造属于自己的测试框架,最终建造出只属于你自己的终极测试框架。**
因此我设计这个课程,就是从接口测试的思维开始,教你把业务测试的思维和接口测试的技术结合到一起,合成接口测试的思维,最终让你拥有接口测试的能力,这种能力既包含了工具的使用、代码的编写同时也包含用例的设计。
## 为什么说接口测试是你的必修课?
那么在测试中,我为什么要强调接口测试的重要性呢?
**从它对项目的影响来说接口测试直接测试后端服务更加接近服务器上运行的代码程序也更能发现影响范围广泛的Bug。**
我记得在《软件测试的艺术》中有这么一句话“越早发现Bug修复的成本越低。”
随着现在中台化、微服务化的发展一套服务支持多种终端例如Android端、iOS端、Web端等这些服务都是由一套后端服务支持的上面这句话就可延伸为“越接近底层的Bug影响用户范围越广泛”。
就如同你发现了一个Web端的界面错误那么这个Bug只会影响Web端用户但是如果后端服务有一个Bug这个Bug有可能会影响所有用户无论他是使用电脑还是手机访问我们的系统。而接口测试就是为了后端服务测试而生的它会保证后端服务的质量避免这种情况出现。
**抛开它对项目的影响,单单从它对你自身的影响来看,你会发现,如今进入任何一个招聘网站,随意点开一个测试工程师的招聘要求,接口测试几乎已经成为测试招聘中一项必备的技能了。**
所以作为一名测试工程师,掌握接口测试,并能熟练完成接口测试,无疑对你在求职时和工作中都有很多好处,比如:
- 增加自己的技能,在找工作时获得更多机会;
- 通过接口自动化完成接口回归测试,让自己的工作更轻松、更高效;
- 通过持续集成平台调用接口自动化测试,为流水线提供质量保障方法和手段,赋能研发。
## 这个专栏是如何设计的?
为了更好地理解和学习接口测试,我来和你说下这个课程的设计思路。
我把整个专栏的内容分成了三大模块,分别是初级技能篇、综合技能篇和进阶技能篇。
- **初级技能篇。**在这里我会带你重新认识接口和接口测试,了解接口测试都包含哪些测试活动,教你如何由测试工程师主导、整理接口测试需要的输入条件。我也会带你合成接口测试技术和业务测试知识,让你形成接口测试思维。
- **综合技能篇。**在这里我会教你如何从流水账式的接口测试脚本,一步步抽象出属于自己的接口测试框架。这样随着课程的深入,你会建立一套自己的接口测试框架,也会同时完成从接口测试到接口自动化测试的转变。最后,我还会告诉你一种借力打力的方法,借助工具的优势,弥补测试框架的缺陷。
- **进阶技能篇。**在这里我会教你如何利用测试工程师的思维,开始并顺利完成一个陌生的协议接口的测试任务,同时将它加入到自己的测试框架中,不断提升它的测试能力。测试框架是你强大的武器,框架数据层的封装则是你的弹药库。所以最后我会教你完成外部依赖解耦的方法和思路,保证你能顺利完成测试任务,并不会因为遇见不靠谱的队友而迟迟无法交付。
测试行业是一个技术驱动的行业,而在测试的工作内容中,接口测试又是一项基础技术能力。
我相信在我们一起学习的这段时间里,我会和你一起学习这项基础技能,我会将你带入一个通向更有价值、更有满足感的任务中,让这个过程中赋予你接口测试的思维和技能;当你完成这个专栏的学习后,你也会带走有你个人标签的接口测试框架,这些都会让你这个行业里身价倍增。
纵观接口测试的发展,这一项技术在最近两年才上了快车道,所以现在上车,你并不算晚。最后,欢迎你在留言区写下你的工作经历,以及你对接口测试的看法;也欢迎你先为你自己的接口测试平台起一个名字,并在留言区先立下自己的目标,我们一起在这个专栏,与一群志趣相投小伙伴相互督促,相互鼓励,一起学习进步,我相信在学习完所有课程后,你一定会顺利完成你的目标!

View File

@@ -0,0 +1,68 @@
<audio id="audio" title="结束语 如何成为一名优秀的测试工程师?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/94/17/94f4f5bc0fccdb66f6d1e2cb0fd86717.mp3"></audio>
你好,我是陈磊。
不知不觉中,我们已经相伴一起学完了接口测试的全部课程,我相信,你现在已经能独立完成绝大部分接口测试的工作了。在这最后一节课呢,我不想再和你说接口测试乃至测试框架还应该做什么,而是想来和你聊聊如何成为一名优秀的软件测试工程师。
在前面的课程中,我一直在和你说测试工程师是个技术岗位,不是“点工”。在把我们的课程全部都学完后,你现在是不是深有同感?因为就接口测试这件小事我们都可以学习这么久,从接口是什么、如何开始接口测试开始,到后来的用工具和代码解决测试问题,直到最后的参数数据层的封装、外部解耦服务的使用等等。
在我们课程的留言中,有这样一个问题:“接口测试能力的高低体现在哪里?”其实,评价一个接口测试人员的能力水平,主要就是看他解决问题的能力如何, 这也包括了他技术栈的熟练程度,测试用例方法以及业务知识的掌握程度。在专栏的全部课程中,我们一起经历了接口测试从简单到深入的过程,而这个过程呢,其实也说明了测试工程师在各个阶段应该达到的能力。这样,测试工程师就可以划分为初级测试工程师、中级测试工程师和高级测试工程师。
**初级测试工程师。**他应该懂得接口测试,可以使用接口测试工具完成接口测试任务;他也要有接口测试的思维,能够将这种思维在实际项目中应用落地。
你也许会质疑这个要求:对于一个初级测试工程师,这会不会太难了?但就像我之前说过的,如果你现在去查一些涉及测试工程师的招聘需求,你就可以看到这项要求,现实就是如此,这部分内容也是我在“初级技能篇”中教给你的东西。
**中级测试工程师。**他要能编写测试代码,可以使用一种编程语言完成接口测试任务。
这短短两句话的要求其实需要很多努力。首先是编码的能力,这在很多测试面前是一个难以逾越的鸿沟,但是如果你跨过这个鸿沟,你就会发现后面会一马平川。
其次,能用编程语言模拟测试协议并完成测试,这确实需要你多了解一下对应编程语言的支持库。
最后,也是最难的要求了,你要能把接口测试思维和技术栈结合一起,并遵从以后的内部编码规范,约束开发的脚本,这其实也是我在“综合技能篇”中给你讲的内容。
**高级测试工程师。**他必须有能力封装适合团队的测试框架,并能提供给持续集成、持续交付平台调用。
要保持自己高级测试工程师的段位,你需要不断努力,不断学习和总结,时刻保持作为一个测试框架维护者的心。在这里,测试框架封装主要就是我在“进阶技能篇”中教给你的框架的抽象,参数化的设计,不同接口、不同协议的支持等内容。
**那么如何使自己不断成长为高级测试工程师呢?**
在留言区,我也看到了很多人对测试工作感到迷茫和困惑,有些人困惑于长期的手动功能测试,自己技术上没有提升,有想学习技术的想法却又不知道如何开始学习;还有些人学了一些技术,却不知道如何把这些技术应用到实际项目中,产生了学习和工作之间的分层,不能学以致用。
其实,在工作中有困惑不是一件坏事,这反而说明你是一个在不断成长的人,因为只有发现问题才能去解决问题。
就接口测试来说学习路线就如同我在专栏中讲解的一样从整理输入到使用工具再到封装框架来完成测试任务这其实也是一个技术的实践路线。如果你不知道如何将这些内容和工作结合到一起不妨试试我在一开始说过的Postman这类小工具用它敲开你的接口测试任务大门随着工作的不断深入后续你在写测试脚本和封装框架也就慢慢水到渠成了。所谓的高级测试工程师也就是这样一步步修炼出来的。
与此同时,我也希望你能通过我们这个专栏的学习思路,学会怎么去掌握任何一项陌生的测试技术,因为这些测试工作的内容可能有所不同,但学习的方法和思路却可以是共通的。
我建议你可以通过三步来规划你的学习:
1. 从实际动手开始学习测试技术。你最好还是从一个实际例子出发开始学习这就和大部分编程语言都从Hello World开始一样。直击问题这样才更能激发你的学习兴趣。
1. 不断遇见问题,不断解决问题。你在实际工作中,肯定会遇见很多问题,你一定要一个一个去解决,并不断地从问题中总结可复用的经验,这样就会形成一套你自己独特的学习思路,以及更适合你自己的技术学习方法,更重要的是,这样学习更不容易遗忘。
1. 掌握理论知识。我之所以将理论知识的学习放到最后,是因为当你知道如何用一个测试技术解决问题的时候,再去学习理论,就不会出现由于理论枯燥、记不住而放弃学习这样的情况了。同时,实践后再学习理论,每看到一个知识点你就会有“原来是这样实现的啊”这种感觉,你就很容易理解并记住它了。
所以说到这,你完全可以把接口测试看作是你技术学习的起点,而不是终点。
我也希望我的课程设计对于你未来的技术学习有一些帮助,在工作中,你肯定会遇见各种各样的新问题,你完全不必担心,因为任何一门技术都是由于工作中出现了痛点,接着才会推动整个技术向前发展,你只要时刻关注自己在工作中碰到的具体问题,还有阻塞工作的问题点,然后学习用技术手段解决问题就可以了。
那么如何解决新问题呢?我建议你学会从自己熟悉的内容出发,在自己的技术或者实践经验基础上去解决。就如同在接口测试中,即使你没有理想的接口测试文档,你也能借助自己熟悉的工具完成接口分析,然后逐渐封装出自己的测试框架。并由此慢慢掌握一个你不是很熟悉的解决问题的方法,最后通过不断地实践积累,不断增加你自己测试框架的测试能力,让它也和你一样,不断解决测试中碰到的新问题。
我们一起学习了这么长的时间,但终归是要为我们的课程画上一个句号,现在我希望你思考一下这几个问题:
<li>
你最初在留言区立下的目标实现了吗?
</li>
<li>
你的测试框架的名字是不是已经有了一个实际的测试框架来承载呢?
</li>
<li>
你掌握接口测试的思维了吗?
</li>
<li>
你掌握了如何学习一项测试技术的方法,并将它应用到实际工作中了吗?
</li>
上面这些问题你只要在心里默默有了答案就好,如果绝大部分问题你都有了肯定答案,那么我要恭喜你并欢迎你走入测试技术的大门,你的未来十分可期。
如果你对接口测试或者其他测试技术还有心得或者问题,那么你要记得,我会一直在留言区等你,这里还有很多和我们一起学习和分享的人,你可以随时回来,和他们分享你的问题和困惑,当然也包括你的成功。
如果这个专栏曾经帮到了你,或正在帮助着你,也欢迎你把它分享给你的朋友和同事,我们一起沟通、探讨。我是陈磊,在前行的路上,记住,你永远也不孤单。

View File

@@ -0,0 +1,10 @@
你好,我是陈磊。
到这里,《接口测试入门课》这门课程已经全部结束了。我给你准备了一个结课小测试,来帮助你检验自己的学习效果。
这套测试题共有 10 道题目包括7道单选题和3道多选题满分 100 分,系统自动评分。
还等什么,点击下面按钮开始测试吧!
[<img src="https://static001.geekbang.org/resource/image/28/a4/28d1be62669b4f3cc01c36466bf811a4.png" alt="">](http://time.geekbang.org/quiz/intro?act_id=169&amp;exam_id=390)

View File

@@ -0,0 +1,379 @@
<audio id="audio" title="04 | 案例:如何把流程化的测试脚本抽象为测试框架?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b2/a0/b2344f6a020eaed426d78059402a44a0.mp3"></audio>
你好,我是陈磊。
在上一节课中我和你强调了,用什么工具或代码解决测试问题并不重要,拥有接口测试思维才更重要。在今天的课程中,我会带你从零开始打造一个测试框架,建立你自己的技术体系。
在这里我推荐你学习一门编程语言以便你可以更加得心应手、个性化地改造你的测试框架或工具。在这节课中我会以Python语言来展示我的代码示例不过语言本身不是重点你只需要了解这其中的逻辑与方法即可同样的事情你可以使用Java、Go等任何你喜欢的语言来完成。
当然如果你想学习Python语言的话我推荐你花一个周末的时间看看[尹会生老师的“零基础学Python”课程](https://time.geekbang.org/course/intro/100008801)。
## 为什么要开发自己的测试框架?
之前我们说到了用Postman来完成接口测试但随着你的接口测试项目逐渐增加你会发现越来越难以管理它的脚本虽然测试工具导出的测试脚本也可以存放到代码仓库但是如果只是通过代码来查看是很难看懂的你必须用原来的测试工具打开才能更容易看懂原来的脚本做了什么样的操作。
同时Postman也有其自身的局限性最重要的一点就是它支持的接口协议有限如果你接到了一个它无法完成的接口类型的测试任务就不得不再去寻找另一个工具。由于接口类型的多样和变化你会有一堆工具需要维护这无疑会提高你的学习成本和技术投入成本。
Postman是如此其他的工具也是如此随着接口测试项目的增加以及被测接口类型的增加维护的难度会成指数级增长所以开发你自己的测试框架非常重要。
今天这节课我就带你用Python 3.7来完成接口测试,并通过测试脚本的不断优化和封装,让你拥有一套完全适合你自己的接口测试框架。当然,我不会告诉你如何写出全部代码,我更想让你掌握的是,从不同的测试脚本抽象出一个测试框架的技巧和思路。
## 搭建测试框架,不要纠结于技术选型
**在做接口测试脚本开发的技术选型上,我更建议你根据自己的技术实力和技术功底来选择,而不要以开发工程师的技术栈来选择。**
这是因为,开发工程师和测试工程师关注的点,以及工作的交付目标是不同的。
- 对于任何一个开发工程师来说,他们主要的工作就是通过写代码实现产品需求或原型设计,他们会关心高并发、低消耗、分布式、多冗余,相对来说,也更加关注代码的性能和可靠性。
- 我们作为测试工程师,无论是使用自动化的接口测试,还是界面的手工测试,第一目标都是保障交付项目的质量,那些业务侧的表现,在大多数情况下不是我们关心的重点。
因此,开发工程师在开发技术栈上的使用频度、使用广度,都会远远高于我们,除非你本来就有对应的知识储备,否则不要强求炫技,为了提高工作效率,你只要使用自己熟悉的技术栈完成自动化接口测试就可以了。
这里我再强调一下,用什么技术栈来写代码,只是一种帮助你实现接口测试的手段,它不是你要交付的结果。所以你在搭建测试框架时,不要太纠结技术选型。
## 搭建前的准备工作
我相信现在你已经准备好,和我一起完成今天的内容了,但在开工之前,我要先把一些基础知识简单介绍给你。
我们今天会用到Python的一个第三方HTTP协议支持库requests它可以让我们在和HTTP协议打交道时更轻松requests项目的描述是“HTTP for Humans”由此可见这会是一个人见人爱的HTTP协议库。你可以通过下面这个命令完成requests的安装
```
pip install requests
```
完成安装后你就可以使用requests完成我用Postman完成的接口测试了。主要代码段我会在文章中给出我会尽最大努力给你一个可以直接运行的代码段这样即使你看不懂也不用担心你只要把这些代码复制到一个有Python运行环境的机器上直接使用就可以了。
第一个接口的单接口测试脚本如下我在代码中做了详细的注释你既可以复制出去直接运行也可以通过注释看懂代码的作用。这样你就完成了一个无参数的、GET访问的验证工作。
```
# Python代码中引入requests库引入后才可以在你的代码中使用对应的类以及成员函数
import requests
# 建立url_index的变量存储战场的首页
url_index='http://127.0.0.1:12356/'
# 调用requests类的get方法也就是HTTP的GET请求方式访问了url_index存储的首页URL返回结果存到了response_index中
response_index = requests.get(url_index)
# 存储返回的response_index对象的text属性存储了访问主页的response信息通过下面打印出来
print('Response内容'+response_index.text)
```
接下来是第二个被测试的接口它是登录接口是以POST方式访问的它需要通过Body传递username和password这两个参数这两个参数都是字符串类型字符长度不可以超过10并且不能为空。
你还记得在上节课中,我们一起用边界值法设计的测试用例吗?如果你忘记了,那么请你在本节课结束后,再回去看一下。这里你用下面的代码段,就可以完成第二个接口的单接口测试脚本了。
```
# python代码中引入requests库引入后才可以在你的代码中使用对应的类以及成员函数
import requests
# 建立url_login的变量存储战场系统的登录URL
url_login = 'http://127.0.0.1:12356/login'
# username变量存储用户名参数
username = 'criss'
# password变量存储密码参数
password = 'criss'
# 拼凑body的参数
payload = 'username=' + username + '&amp;password=' + password
# 调用requests类的post方法也就是HTTP的POST请求方式
# 访问了url_login其中通过将payload赋值给data完成body传参
response_login = requests.post(url_login, data=payload)
# 存储返回的response_index对象的text属性存储了访问主页的response信息通过下面打印出来
print('Response内容' + response_login.text)
```
无论你是不是看得懂上面的两段代码,你都能看出来,这其中有很多代码都是重叠在一起的,这两段代码的结构很相似,但又有明显的差异性。
## 开始打造一个测试框架
我想请你先思考这么一个问题你在用Postman这类工具做接口测试时除去你自己构建的访问路由和Requsts参数其他的是不是就靠工具帮你处理完成了呢
那么我们接口测试的脚本是不是也可以把一些公共的操作抽象到一个文件中呢这样你在写测试脚本时通过拼凑路由、设计Request入参就可以完成接口测试了。在这样的思路之下我们来一起改造一下刚刚的脚本。
第一步你要建立一个叫做common.py的公共的方法类。下面我给出的这段注释详细的代码就是类似我们使用Postman的公共方法的封装它可以完成HTTP协议的GET请求或POST请求的验证并且和你的业务无关。
```
# 定义一个common的类它的父类是object
class Common(object):
# common的构造函数
def __init__(self):
# 被测系统的根路由
self.url_root = 'http://127.0.0.1:12356'
# 封装你自己的get请求uri是访问路由params是get请求的参数如果没有默认为空
def get(self, uri, params=''):
# 拼凑访问地址
url = self.url_root + uri + params
# 通过get请求访问对应地址
res = requests.get(url)
# 返回request的Response结果类型为requests的Response类型
return res
# 封装你自己的post方法uri是访问路由params是post请求需要传递的参数如果没有参数这里为空
def post(self, uri, params=''):
# 拼凑访问地址
url = self.url_root + uri
if len(params) &gt; 0:
# 如果有参数那么通过post方式访问对应的url并将参数赋值给requests.post默认参数data
# 返回request的Response结果类型为requests的Response类型
res = requests.post(url, data=params)
else:
# 如果无参数,访问方式如下
# 返回request的Response结果类型为requests的Response类型
res = requests.post(url)
return res
```
接下来用你自己的Common类修改第一个接口的单接口测试脚本就可以得到下面的代码了。
```
# Python代码中引入requests库引入后才可以在你的代码中使用对应的类以及成员函数
from common import Common
# 首页的路由
uri = '/'
# 实例化自己的Common
comm = Common()
#调用你自己在Common封装的get方法 返回结果存到了response_index中
response_index = comm.get(uri)
# 存储返回的response_index对象的text属性存储了访问主页的response信息通过下面打印出来
print('Response内容' + response_index.text)
```
从这段代码中你可以看到与前面对应的单接口测试脚本相比代码的行数有明显的减少这也能减少你很多的工作量与此同时如果你有任何关于HTTP协议的操作都可以在Common类中进行修改和完善。
如果使用你自己刚刚建立的公共类在我们内部有时候喜欢把它叫做轮子这是源于一句俚语“不用重复造轮子”因为Common类就是重复被各个检测代码使用的“轮子”修改一下第二个接口的单接口测试脚本代码就会变成下面这个样子
```
#登录页路由
uri = '/login'
# username变量存储用户名参数
username = 'criss'
# password变量存储密码参数
password = 'criss'
# 拼凑body的参数
payload = 'username=' + username + '&amp;password=' + password
comm = Common()
response_login = comm.post(uri,params=payload)
print('Response内容' + response_login.text)
```
当你有一些更加复杂的脚本时,你会发现两次代码的变化会变得更明显,也更易读。
这就是那些曾经让你羡慕不已的框架诞生的过程,通过分析和观察你可以看到,原始的第一个接口的单接口测试脚本和第二个接口的单接口测试脚本,它们存在相同的部分,通过将这些相同的部分合并和抽象,就增加了代码的可读性和可维护性,也减少了脚本的开发量。通过这个方法,你就可以打造出一个属于自己的测试框架。
## 用你的框架完成多接口测试
上面我们仅仅进行了一小步的封装,就取得了很大的进步,在你写出越来越多的脚本后,你还会发现新的重叠部分,这时如果你能不断改进,最终就会得到完全适合你的测试框架,而且其中每一个类、每一个函数你都会非常熟悉,这样,碰到任何一个难解的问题时,你都有能力通过修改你的框架来解决它,这样,这个框架实际上就变成了一个你在接口测试方面的工具箱了。
那么,怎么用我们刚刚一起搭建的测试框架,来完成多接口测试的业务逻辑测试呢?
不知道你是不是还记得在上节课中我们讲到的Battle使用流程的测试用例如果你没记起来我先告诉你“正确登录系统后选择武器与敌人决斗后杀死了敌人。”其他的在本次课程结束后你可以自己再去温习一下。
那么。使用我们一起封装的框架来完成上面的多接口测试后,就会得到下面的代码:
```
# Python代码中引入requests库引入后才可以在你的代码中使用对应的类以及成员函数
from common import Common
# 建立uri_index的变量存储战场的首页路由
uri_index = '/'
# 实例化自己的Common
comm = Common()
#调用你自己在Common封装的get方法 返回结果存到了response_index中
response_index = comm.get(uri_index)
# 存储返回的response_index对象的text属性存储了访问主页的response信息通过下面打印出来
print('Response内容' + response_index.text)
# uri_login存储战场的登录
uri_login = '/login'
# username变量存储用户名参数
username = 'criss'
# password变量存储密码参数
password = 'criss'
# 拼凑body的参数
payload = 'username=' + username + '&amp;password=' + password
comm = Common()
response_login = comm.post(uri_login,params=payload)
print('Response内容' + response_login.text)
# uri_selectEq存储战场的选择武器
uri_selectEq = '/selectEq'
# 武器编号变量存储用户名参数
equipmentid = '10003'
# 拼凑body的参数
payload = 'equipmentid=' + equipmentid
comm = Common()
response_selectEq = comm.post(uri_selectEq,params=payload)
print('Response内容' + response_selectEq.text)
# uri_kill存储战场的选择武器
uri_kill = '/kill'
# 武器编号变量存储用户名参数
enemyid = '20001'
# 拼凑body的参数
payload = 'enemyid=' + enemyid+&quot;&amp;equipmentid=&quot;+equipmentid
comm = Common()
response_kill = comm.post(uri_kill,params=payload)
print('Response内容' + response_kill.text)
```
上面的代码有点长,但你先不要有抵触的心理,每一个代码行的注释我都写得很清楚。然而我并不是想让你知道,上面那么多类似蝌蚪文的代码都是干什么的,我是想让你看看上面的代码中,是否有可以用前面“抽象和封装重复代码的方法”进行优化的地方。
你可以看到上面的代码大量重复了你自己写的通用类的调用这个其实是可以合成一个的同时你再观察一下我们一起写的Common类你会发现有一个self.url_root = [http://127.0.0.1:12356](http://127.0.0.1:12356)如果这里这样写你的Common就只能用来测试我们这个小系统了除非你每次都去修改框架。
但是,任何一个框架的维护者,都不希望框架和具体逻辑强相关,因此这也是一个优化点,那么将上面的内容都修改后,代码就会变成下面这个样子:
```
# Python代码中引入requests库引入后才可以在你的代码中使用对应的类以及成员函数
from common import Common
# 建立uri_index的变量存储战场的首页路由
uri_index = '/'
# 实例化自己的Common
comm = Common('http://127.0.0.1:12356')
#调用你自己在Common封装的get方法 返回结果存到了response_index中
response_index = comm.get(uri_index)
# 存储返回的response_index对象的text属性存储了访问主页的response信息通过下面打印出来
print('Response内容' + response_index.text)
# uri_login存储战场的登录
uri_login = '/login'
# username变量存储用户名参数
username = 'criss'
# password变量存储密码参数
password = 'criss'
# 拼凑body的参数
payload = 'username=' + username + '&amp;password=' + password
response_login = comm.post(uri_login,params=payload)
print('Response内容' + response_login.text)
# uri_selectEq存储战场的选择武器
uri_selectEq = '/selectEq'
# 武器编号变量存储用户名参数
equipmentid = '10003'
# 拼凑body的参数
payload = 'equipmentid=' + equipmentid
response_selectEq = comm.post(uri_selectEq,params=payload)
print('Response内容' + response_selectEq.text)
# uri_kill存储战场的选择武器
uri_kill = '/kill'
# 武器编号变量存储用户名参数
enemyid = '20001'
# 拼凑body的参数
payload = 'enemyid=' + enemyid+&quot;&amp;equipmentid=&quot;+equipmentid
response_kill = comm.post(uri_kill,params=payload)
print('Response内容' + response_kill.text)
是不是比上一个节省了很多代码同时也看的更加的易读了那么我们封住好的Common就变成了如下的样子
# 定义一个common的类它的父类是object
class Common(object):
# common的构造函数
def __init__(self,url_root):
# 被测系统的跟路由
self.url_root = url_root
# 封装你自己的get请求uri是访问路由params是get请求的参数如果没有默认为空
def get(self, uri, params=''):
# 拼凑访问地址
url = self.url_root + uri + params
# 通过get请求访问对应地址
res = requests.get(url)
# 返回request的Response结果类型为requests的Response类型
return res
# 封装你自己的post方法uri是访问路由params是post请求需要传递的参数如果没有参数这里为空
def post(self, uri, params=''):
# 拼凑访问地址
url = self.url_root + uri
if len(params) &gt; 0:
# 如果有参数那么通过post方式访问对应的url并将参数赋值给requests.post默认参数data
# 返回request的Response结果类型为requests的Response类型
res = requests.post(url, data=params)
else:
# 如果无参数,访问方式如下
# 返回request的Response结果类型为requests的Response类型
res = req
```
你可以看到在上面这段代码中我主要是让我们Common类的构造函数接受了一个变量这个变量就是被测系统的根路由。这样是不是就比上一个代码段节省了很多代码同时也更加易读了那么我们封装好的Common就变成了下面这个样子
```
# 定义一个common的类它的父类是object
class Common(object):
# common的构造函数
def __init__(self,url_root):
# 被测系统的跟路由
self.url_root = url_root
# 封装你自己的get请求uri是访问路由params是get请求的参数如果没有默认为空
def get(self, uri, params=''):
# 拼凑访问地址
url = self.url_root + uri + params
# 通过get请求访问对应地址
res = requests.get(url)
# 返回request的Response结果类型为requests的Response类型
return res
# 封装你自己的post方法uri是访问路由params是post请求需要传递的参数如果没有参数这里为空
def post(self, uri, params=''):
# 拼凑访问地址
url = self.url_root + uri
if len(params) &gt; 0:
# 如果有参数那么通过post方式访问对应的url并将参数赋值给requests.post默认参数data
# 返回request的Response结果类型为requests的Response类型
res = requests.post(url, data=params)
else:
# 如果无参数,访问方式如下
# 返回request的Response结果类型为requests的Response类型
res = requests.post(url)
return res
```
通过改造Common类的构造函数这个类已经变成一个通用类了无论是哪一个项目的接口测试都可以使用它来完成HTTP协议的接口验证了。
我相信现在你已经掌握了测试框架的形成过程,就如下图所示,测试框架的形成是在撰写大量测试脚本的过程中不断抽象封装出来的,然后,再用这个不断完善的框架,改写原有的测试脚本。循环往复这个过程,你就会慢慢获得一个独一无二的、又完全适合你工作的接口测试框架。
<img src="https://static001.geekbang.org/resource/image/d6/7e/d6066b2a101012f4fac20160d386a57e.jpg" alt="">
其实到这里,我们上面说的只能算是一个调试代码,还不能算是一个测试框架。上面这些代码所有的返回值都打印到控制台后,为了完成接口测试,你需要时时刻刻看着控制台,这还不能算是自动化,只能说是一个辅助小工具。
在这里你应该让全部测试结果都存储到测试报告里面同时通过一个测试驱动框架来完成各个模块的驱动这也是为什么你在学习任何一种框架的时候总会遇见类似Java的JUnit、Python的Unittest的原因因此上面的Common类还需要和Python的unittest一起使用才算是一个完美的测试框架。
至于你自己的Common类怎么和测试驱动框架相结合这部分内容就留给你在未来的接口测试工作中自己去学习并完成了。
## 总结
今天,我们一起学习了一个测试框架的诞生过程。测试框架就是在你测试脚本中不断抽象和封装得来的。今天我们课程的内容充斥着各种代码,如果你的代码基础稍微比较薄弱,并没有完全记住上面的内容,那么我希望你记住从测试脚本到测试框架的转化过程:
1. 不断撰写测试脚本,所有的抽象和封装都是站在已有的测试脚本基础之上的;
1. 多观察已经写好的测试脚本,找出其中的重叠部分,最后完成封装;
1. 以上两步是一个不断循环又循序渐进的过程,你要在你的工作中始终保持思考和警惕,发现重复马上进行框架封装。
最后我想和你强调的是,测试框架的封装和抽象过程并不是一蹴而就的,它是靠一点一点的积累得来的,因此,你要通过自己的实践,慢慢积累和完善你的测试框架,而不要妄想一次就能有一个完善的测试框架。我相信,当你通过写脚本完成整个项目的接口测试后,你一定会得到一个完美的测试框架。
## 思考题
在我讲的最后一个多接口测试脚本,其实也并不是最完美的修改,你能提出更好的修改意见吗?如果它可以抽取到你的框架中,那么是完成一个什么样任务的类或者函数呢?
我是陈磊,欢迎你在留言区留言分享你的观点,如果这篇文章让你有新的启发,也欢迎你把文章分享给你的朋友,我们一起探讨和学习。

View File

@@ -0,0 +1,126 @@
<audio id="audio" title="05 | 案例测试框架如何才能支持RESTful风格的接口" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3a/0f/3a36390dd9c9b0d8a1ca43481f4bf50f.mp3"></audio>
你好,我是陈磊。
在前面的课程中,我们一起学习了如何把流程化的测试脚本,一步一步抽象成你自己的测试框架。无论你用的是什么编程语言,封装和抽象自己的测试框架都会让你的接口测试任务事半功倍。
我相信你在平时生活或工作中一定会接触到各式各样的软件系统而现在的软件系统和5年前相比最大差别就在于结构不同。
在我读大学的时候绝大部分系统还都是用一个Tomcat来搞定的但现在的系统更加复杂它们已经无法只用一个Web中间件独立对外提供服务它们之间都也是通过相互调用来完成业务逻辑的这里面既包含了服务端和服务端的调用也包含了前端和服务端的调用这就催生了RESTful风格的HTTP接口。
所以这节课我就来和你讲讲如何让你的测试框架完美支持RESTful风格的接口测试。这里我希望你能不断强化封装测试框架的三个流程不断为自己的接口测试框架添砖加瓦。
不过我不会将RESTful的规则一条一条念给你听我想让你知道的重点是作为测试工程师你要学会从测试工程师的角度观察RESTful接口要学会怎么分析和验证这类接口这也是今天我们今天这节课的主要内容。
## RESTful风格接口关我什么事
看到这里,你是不是一脸困惑:**RESTful是一个接口的封装风格和我们测试人员有什么关系呢**
要想理解它和我们测试工程师的关系你就要先知道RESTful风格的接口到底有什么好。
如果你用螺丝、钉子和板材等一系列原材料组装过家具,那么你肯定见到过各种千奇百怪的螺丝,比如一字的、十字的、三角形的、六角形的和海星形的等等,为了加固这些各式各样的螺丝,你就要准备各式各样的螺丝刀,因此,你的工具箱就会被不同规格和大小的螺丝刀填满。
不知道你是不是也和我一样,面对塞满螺丝刀的、乱七八糟的工具箱,心里非常急躁。但后来我在宜家看到一款螺丝刀,它只有一个刀柄,但给你提供了一整套各种形状、各种大小的螺丝刀刀头。
这样你在使用时,只要根据螺丝规格的不同,选择替换同形状的刀头就可以了;与此同时,它们放在工具箱里面又会显得很整齐,而不会七零八落。而且,如果你后续需要使用其它特殊形状的螺丝刀,你只要买和刀柄连接口一样的刀头就可以了,而不用再买一个完整的螺丝刀了。
如果你理解了上面这个场景也就能很好地理解RESTful风格的接口了。**它主要就是一组设计原则和约束条件本质上就是让消费者依据URI就可以找到资源并通过简单的服务输入输出完成服务的交互。**
它所约束的每一个URI都是独一无二的一个资源通过HTTP的方法进行资源操作实现表现层的状态转化。这就和螺丝刀刀头一样待解决的问题就像螺丝每一个接口只面向一种特定的资源而不需要关心其他接口的处理方式这样你就能够一目了然地知道该用哪种螺丝刀头拧哪种螺丝了这就降低了接口开发的复杂度。
软件开发人员只要遵循RESTful的实践标准按照一定的内部定义开发外接口就会形成类似螺丝刀刀头一样轻便的接口对外提供服务。现在的很多项目无论是服务端和服务端的调用还是前端和服务端的调用都采用了这一种方式来设计接口。
对于我们测试工程师来说RESTful风格的接口还是之前的访问模式它同样是一种HTTP协议的接口同样可以使用我们上节课一起封装的框架完成接口测试的任务。
但是它和我们之前讲过的HTTP协议的接口测试还是有一些区别这些区别导致了你现在的框架还需要做一些修改这样才能让它支持RESTful风格的接口测试。
## 让你的框架可以测试一个RESTful风格接口
现在你知道RESTful接口和你的接口测试有很大关系了那么RESTful接口的测试和原始的HTTP协议接口的测试又有什么区别呢
这里面有两部分需要你特别关注:**数据交换的承载方式和操作方式**。
我先说说数据交换的承载方式RESTful风格的接口主要是以JSON格式来进行数据交换如果你还记得我之前提过的“Battle”这个系统你可以回到[03](https://time.geekbang.org/column/article/193912)中查看它那么你一定在它的Readme.md中看到了Request和Response对数据部分的一些定义那就是JSON。虽然“战场”不能算是一个严格的RESTful接口的系统但是在数据交接的承载方式上它模仿了RESTful的样子。
另外一个部分是操作方式在“战场”系统中我们用了HTTP协议的Get和Post其实HTTP协议有很多方法但是我们仅仅用了这两种而RESTful的规定使HTTP的很多方法都被利用到了比如说Get方法用来获取资源Post方法用来新建资源或者更新资源再比如说Put方法用来更新资源、Delete方法用来删除资源等等。
在明白RESTful风格的接口和普通的HTTP协议接口的区别后我们现在来想一想你自己的框架需要添加什么内容才能支持RESTful风格的接口呢**这里我总结了两个方法:借助外力和自己封装。**
### 借助外力
这里我们RESTful的第一个数据交换的承载方式是JSON我们的框架在之前的“战场”系统中就已经使用了它虽然全部的操作都是参数拼凑的过程但这已经满足了我们的需求。
这时如果你仍要拼凑很多复杂的数据就需要使用JSON字符串和代码对象实体的转换它有一个专业的叫法**序列化和反序列化**。这个词语听着就很难理解,所以现在,我用一个生活中的小例子来告诉你,这个晦涩难懂的概念到底是什么意思。
如果你在商场看中了一款衣柜,但它很大,为了方便运输,就必须要先把它拆掉,运到家后再重新组装。你和商家协商好了,由他们为你把这个衣柜拆成可重组的零件运到家里,然后由你自己把这些零件重新组装成一个衣柜。
那么在这里商家把衣柜拆成各个零件、然后打包的这个过程就是“序列化”在代码中就是将一些程序对象转换成JSON等格式的字符串的过程。接下来你用这些零件再重新组装成一个衣柜这个过程就是“反序列化”在代码中就是JSON等格式的字符串转换成程序的对象的过程。
为了能让你的框架可以快速完成序列化和反序列化我建议你在代码中引入一个外部支持的库就像Python有JSON库、Java有Fastjson库。这些公开的库其实都不需要做任何的修改就可以拿来使用所以无论你使用哪种技术栈这样的基础库都是存在的你只需要在网上找一下然后花几分钟看一下怎么使用就可以拿到自己的框架里使用了。
### 自己封装
现在我们已经可以借助开源库解决数据交换的事情了但是RESTful风格接口和普通HTTP接口相比还有一个明显的区别那就是RESTful规定了HTTP的每一个方法都做固定的事情可我们原来框架中的Common类却只支持Get和Post方法因此你需要在Common类中加入Delete和Put方法的支持。具体的操作你可以依据下面这个代码段来完成
```
def put(self,uri,params=None):
'''
封装你自己的put方法uri是访问路由params是put请求需要传递的参数如果没有参数这里为空
:param uri: 访问路由
:param params: 传递参数string类型默认为None
:return: 此次访问的response
'''
url = self.url_root+uri
if params is not None:
# 如果有参数那么通过put方式访问对应的url并将参数赋值给requests.put默认参数data
# 返回request的Response结果类型为requests的Response类型
res = requests.put(url, data=params)
else:
# 如果无参数,访问方式如下
# 返回request的Response结果类型为requests的Response类型
res = requests.put(url)
return res
def delete(self,uri,params=None):
'''
封装你自己的delete方法uri是访问路由params是delete请求需要传递的参数如果没有参数这里为空
:param uri: 访问路由
:param params: 传递参数string类型默认为None
:return: 此次访问的response
'''
url = self.url_root + uri
if params is not None:
# 如果有参数那么通过delete方式访问对应的url并将参数赋值给requests.delete默认参数data
# 返回request的Response结果类型为requests的Response类型
res = requests.delete(url, data=params)
else:
# 如果无参数,访问方式如下
# 返回request的Response结果类型为requests的Response类型
res = requests.delete(url)
return res
```
在上面的代码中你可以看到我们为了实现HTTP协议的Put和Delete方法自己封装了put()函数和delete()函数。其实要实现RESTful风格的接口测试你只要封装HTTP协议对应的Method方法就可以了这样你的框架就能完美的支持RESTful风格的接口了。完成了这个操作后我们的Common类就既可以完成HTTP协议接口的测试也可以完成RESTful接口的测试了。
## 总结
到这里我们已经结束了今天的课程了。我们今天主要完成了RESTful风格接口的测试对比之前的例子以及你自己的测试框架针对框架中RESTful里缺失的部分我为你提供了对应的解决方法。
在文中我讲了很多内容但是完成RESTful风格接口测试主要是通过两步操作来为你的测试框架添加对应接口的测试能力的
1. 借助外力。目前网上已经有很多成熟的、各式各样的支持库,你要尽量拿来为己所用,而不要从零建设,这样,既弥补了我们开发能力不强的短板,也能提高我们的研发效率。
1. 自己封装。你要注意的是,自己封装和借助外力并不互相冲突,你要借助外力,然后将它封装到你自己的框架中,这是一个借力打力的好方法。
随着我们的课程的不断深入以及内容的不断丰富,我相信,你最终会获得一个完全适合你自己,又可以解决实际工作任务的测试框架,这也是你自己的接口测试武器仓库,里面有解决各种接口测试问题的方法。它会是一个私有仓库,里面每一个武器都是为你自己量身定制的,因此,每一件武器你用起来都会更得心应手。
## 思考题
我今天讲了RESTful接口测试并为你的私有测试框架添加了各式各样的新武器那么你能用你现在的新武器解决一个你负责的RESTful的接口测试吗在今天的框架中随着你实际工作的使用你又有了什么样的新设计呢
我是陈磊,欢迎你在留言区留言分享你的观点,如果这篇文章让你有新的启发,也欢迎你把文章分享给你的朋友,我们一起探讨和学习。

View File

@@ -0,0 +1,167 @@
<audio id="audio" title="06 | 接口测试平台:工具和框架不可以兼容?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/35/77/351b551ef09295f90729b9151d760e77.mp3"></audio>
你好,我是陈磊。很高兴在接口测试课程中再次遇见你。
到目前为止我们的课程重点介绍了完成测试任务的两种接口测试手段第一种是使用如Postman这样的工具第二种是打造属于你自己的测试框架。上节课我们还一起学习了RESTful风格的接口并针对它的特点完善了我们自己的测试框架。
这节课我就教你如何用工具和框架的组合搭建接口测试平台,让你能更快速地完成测试任务。
## 工具的便捷性与框架的灵活性可以兼得
说到这儿你一定有一个困惑在前面我先讲了Postman这款非常好用的HTTP测试工具后来又讲了怎么自己动手封装接口测试框架它们各有特点比如工具有便捷性框架有灵活性这无疑是两条都可以通向罗马的路是两种都可以完成接口测试工作的方法那学会一个不就可以了为什么两个都要学会呢
而且工具和框架,这两件事看起来互不相干,甚至有些互相排斥,那么这两种接口测试技术手段能相互支持,能融合到一起吗?下面我就来回答这个问题。
其实工具和框架这两条通向罗马的路可以并成一条快速通道让你大踏步进军罗马。所以我既建议你要掌握一款好用的工具比如Postman也建议你用自己的技术沉淀出自己的框架如果你能正确地混合使用它们实质上就可以搭建起一个接口测试平台帮你更快速地完成测试任务。
在脚本的设计过程中,这样做有两个好处。
一是能充分发挥Postman界面化的优势快速完成大量的脚本撰写工作二是通过你自己的框架完成测试脚本的执行所有的过程代码都会存储到你自己的代码仓这样既可以留下测试的过程资产也便于版本控制这也为持续集成、持续交付等平台提供了无人值守的、按需驱动测试的途径。<br>
<img src="https://static001.geekbang.org/resource/image/d2/06/d2658558e998a79c9bded28fb129cd06.jpg" alt="">
此外,这样做也可以提升团队的工作效率。
在我的团队中,有一些小伙伴很不喜欢写代码,相反,他们更喜欢使用工具,工具用得也非常溜,我相信在你的团队中也会存在这样的情况。但是,仅仅依靠工具,只能一个人完成一件事情,这并不方便团队内部的团队合作、交接和技术积累。
但是,我们又没有办法让所有人一下子都喜欢上写代码,那么该如何降低代码编写门槛呢?通过工具和框架搭建接口测试平台,其实就是一个很好的解决方案。这样,你既可以让你的团队有技术积累,又能给团队中一些编码能力比较薄弱的小伙伴学习时间,最重要的一点是,这不会影响整个工作的进度。
## 工具的便捷性可得
不知道在学完前面的课程后你是不是还用过自己的Postman当你再次打开Postman的时候会发现你之前用它来完成的测试脚本是被保存下来了的就如下图所示
<img src="https://static001.geekbang.org/resource/image/f0/f0/f08eff06a9649715d683ac72793878f0.png" alt="">
可以看到我上次使用Postman测试“战场”系统的脚本还是存在的如果你忘记了可以在课后看一看你用来做测试的Postman再分别看看那四个请求。
到这里我想请你先闭上眼睛回想一下你之前使用Postman做接口测试的整个流程是不是清晰可见你也可以同时回忆一下你用自己封装的Common类编写“战场”系统测试脚本的过程。你会发现和工具的使用过程相比在这里你不太容易回想出自己每一步都做了什么。
这也是UI操作和代码操作的区别之一UI操作更加直观可以在你的脑海里留下更深刻的印象而代码操作给人留下的印象就比较模糊但是通过用代码写脚本来完成接口测试比较便于维护、团队合作以及留存。
讲到这你肯定会问“你把用Postman类工具完成接口测试以及自我封装测试框架这两种方法各打五十大板那它们到底哪个好”其实我的目的并不是想让你分出个好坏好坏之分都是相对的每个人的习惯和喜好都不相同但是我们却可以把它们的优点都利用好把这两种技术的优势都发挥出来。
我们利用Postman设计接口测试直观、快速的优势将它变成接口测试脚本的初始脚本的编写工具其实Postman也可以配置Chrome插件录制请求这些在Postman官方已经有很详细的介绍所以我就不在这里详细讲解了如果你感兴趣课后可以自行学习。
我们以之前的测试脚本为例选择第一个单接口接口测试的脚本在右侧点击Code按钮。
<img src="https://static001.geekbang.org/resource/image/c8/f3/c800540c1ca21ef7d362bacc88bbc8f3.png" alt="">
在弹出框中你可以选择各式各样技术栈的测试脚本在这里我们还是用在之前例子中所选取的Python我们框架的依赖库是Requests这样你就可以看到显示出的代码了就如下图所示。看到这些代码你是不是已经开始觉得通过这样的处理来编写脚本更加容易。
<img src="https://static001.geekbang.org/resource/image/21/95/2174ecbad6cefba4d166f12a1f21aa95.png" alt="">
由此可见和写代码相比使用Postman来设计接口测试要更容易使用对于代码基础比较薄弱的测试工程师来说这种方法也更容易掌握。
## 框架的灵活性亦可得
在刚刚高兴的心情慢慢冷静下来以后你是不是在心里默默地埋怨我既然有这么简单的方法为什么还一直让你学习一门编码技术还建议你如果什么都不会可以学习一下Python这是因为工具生成的代码可读性特别差也并不适合我们将它作为团队的技术积累留存。
现在,我们一起看一看由工具生成的代码。先来看看第一个接口首页单接口对应的代码:
```
import requests
url = &quot;http://127.0.0.1:12356&quot;
headers = {
'cache-control': &quot;no-cache&quot;,
'Postman-Token': &quot;8c6247bb-744a-43d3-b27d-9e51af923c5d&quot;
}
response = requests.request(&quot;GET&quot;, url, headers=headers)
print(response.text
```
上面的这个代码你是不是似曾相识?这就和我们第一次写的第一个接口的单接口测试代码一样,是一个流水账一样的脚本,这些代码如果原模原样地存到你的代码仓中,对你再次使用它没什么好处。那么在这基础上,我们可以将它修改成自己框架的脚本,就如下面这段代码所示:
```
# 引入你的框架
from common import Common
#访问uri
uri_index = &quot;/&quot;
#调用你的Common类
comm = Common('http://127.0.0.1:12356')
# 完成方法
response_login = comm.get(uri_index)
# 打印response结果
print('Response内容' + response_login.text)
```
这个代码你是不是很亲切Common类可是我们的老朋友了。那么接下来我们再看看第二个接口登录的单接口测试脚本你可以用相同的方法找到它的Python代码为了方便有些不是很方便打开自己Postman的同学我把对应的代码放到了下面
```
import requests
url = &quot;http://127.0.0.1:12356/login&quot;
payload = &quot;username=criss&amp;password=criss&quot;
headers = {
'cache-control': &quot;no-cache&quot;,
'Postman-Token': &quot;fdc805e1-4406-4191-ae44-ab002e475e03&quot;
}
response = requests.request(&quot;POST&quot;, url, data=payload, headers=headers)
print(response.text)
```
如果你还记得我在测试代码及框架那一节课(也就是[04](https://time.geekbang.org/column/article/195483)中讲的内容就会发现它和那里最开始部分的代码实现几乎一致这和我们自己手动写的代码最大区别就是它少了很多注释而多出一些访问头信息也就是上面代码的headers。
headers在我们“战场”系统的测试中并不是必须传递的参数但是Postman这种工具会将其添加默认值传递给服务器。这是由这个工具添加的你在写脚本时如果它是非必填的你可以忽略它。但是工具这么做是为了匹配所有的情况所以它会做一些和我们这次测试不相干的工作。
难道说Postman这么好的功能对我们来说就没有一点好处吗其实我们在上面代码的基础上将其修改成引入我们自己框架的测试代码完成修改后再推送到接口测试项目的代码仓中就如下面这个代码所示
```
from common import Common
uri = &quot;/login&quot;
payload = &quot;username=criss&amp;password=criss&quot;
comm = Common('http://127.0.0.1:12356')
response_login = comm.post(uri_login,params=payload)
print('Response内容' + response_login.text)
```
这也无疑加快了我们测试脚本的编写,在上面这个过程中,我们也很容易再次发现需要封装到框架中的公共方法,这样循环下来,就加快了我们测试脚本的积累速度,同时我们也就可以有更多时间用在框架的维护上了。
通过代码的改写和封装我们就可以将工具生成的代码完美结合到我们的框架中了当我们需要修改已经存储在代码仓库的脚本时我们只需pull 代码仓的代码,就可以看到易读、易维护的测试脚本了。
## 总结
我们今天的课程到这里就结束了现在你闭上眼睛回顾一下如果你的头脑中就只有用Postman快速编写脚本自己框架留存执行的话只能说明你今天学习得很认真但是并没接受我想告诉你的主旨思想。
我今天以Postman工具和你自己的框架相结合的例子告诉你如何建立一个你自己的测试平台你可以通过三步完成工具加框架的组合方式
1. 借助Postman这类工具的易学、易操作的特点将它变成你测试脚本中快速创建的脚本撰写工具
1. 利用工具提供的导出代码功能,将其导出成我们流程化的测试代码;
1. 通过我们自己的框架,改写我们通过工具导出的脚本。
最后,你的测试脚本可以存入代码仓中为持续集成平台提供持续验证,这就完成了一套简单又灵活的接口测试平台的建设。
实际上,在本节课中,我更希望帮你建立一种解决问题思路,测试工程师的技术普遍会稍微弱于开发工程师,你要善于利用各种技术手段来帮助自己解决问题。
无论你团队中的小伙伴是用Postman生成测试脚本再通过修改集成自己的框架还是直接通过框架写测试脚本它们都殊途同归都是以最终统一的方式推送到了代码仓库中。这样就不会让代码能力变成阻塞最终工作的一个关键节点同时这对于使用Postman编写脚本的小伙伴来说他们也会越来越熟悉自己的框架逐渐提升自己的技术能力并加强自己的代码能力。
## 思考题
今天的课程中,我们把“战场”系统的测试脚本又拿出来测试了一次,不知道你是不是有点厌烦了,今天我仅仅给你演示了第一个和第二个单接口测试脚本,它们从工具到框架的演变过程,那么,你可以将后面的两个单接口测试脚本自己完成,并在留言区回复给我吗?如果你已经开始将这个方法应用到你的工作中了,那么我也希望你能将自己在使用过程中的心得体会分享给我。
我是陈磊,欢迎你在留言区留言分享你的观点,如果这篇文章让你有新的启发,也欢迎你把文章分享给你的朋友,我们一起来探讨和学习。

View File

@@ -0,0 +1,231 @@
<audio id="audio" title="07 | WebSocket接口如何测试一个完全陌生的协议接口" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8c/fb/8cd3f08efe6d3b418ae85932636767fb.mp3"></audio>
你好,我是陈磊。很高兴你又来和我一起探寻接口测试的奥秘了。
我们在前面一起学习了怎么分析和完成一个HTTP协议的接口测试又一起学习了如何封装接口测试框架以及如何搭建接口测试平台。我相信现在你已经完全掌握了HTTP协议的接口测试了。
但是这还不能说明你已经能独立完成接口测试了为什么这么说呢这是因为数据在网络传输上都是依靠一种协议来完成的在上学的时候你肯定学过包括TCP、UDP、HTTP等在内的一堆协议但是如果你遇见了一个全新的协议你知道怎么从零开始完成接口测试吗
今天我就以WebSocket为例告诉你当你第一次接触一个完全陌生的协议接口时你要如何开始完成接口测试工作。
## 未知的新协议接口并不可怕
作为一名测试工程师,在面对一个陌生协议的接口测试时,你是不是会常常感到很无助?面对这样的任务时,你的第一反应肯定是向开发工程师求助,因为开发工程师基于新协议已经完成了接口开发,向开发工程师求助显然是最好的办法。
我在工作中就遇见过类似的事情。记得那是在我参加工作的前几年,有一个被测项目的接口是一个私有协议,当我看到接口文档的时候,第一反应就是找开发工程师,向他求教一下这个私有协议。这个开发工程师人很好,他给了我一个学习脉络,其中包含了协议的说明文档、代码开发文档、实现代码等内容,我拿到这些资料后,马上按照上面他给出的学习顺序投入学习。
但是后来,在项目从交付测试到完成测试后,我发现自己走了一个弯路。因为作为测试工程师,我们不需要了解协议底层的原理,只需要了解新协议是如何传输数据,又如何返回数据库就可以了。也就是说,我们要想模拟一个客户端去验证服务端的逻辑,那么开始接口测试最快速的方法不是去看协议的说明文档,而是直接去看开发实现的客户端代码,这对于我们来说,能更直接地解决问题。但这也并不是说,那位开发工程师告诉我的学习脉络是错误的,只不过它并不是一个非常适合测试工程师的学习方法。
那在面对一个陌生的新协议时,测试工程师的首要任务是什么呢?
**在我看来,就是要测试接口的正确逻辑、错误逻辑是否满足最初的需求,因此,我们需要快速地掌握验证手段。**在时间紧迫的情况下,如果我们还是先学习新协议的基础知识,再学习怎么使用它,就无疑压榨了测试的工期,也会让我们在真正开始工作时手忙脚乱。
所以,我们要从解决实际问题的角度出发,直接拿到开发工程师提供的调用客户端代码,这样我们就可以快速完成工作了;在完成工作的后续时间里,我们也可以慢慢补充基础知识。这里需要你注意的是,我并不是说基础知识不重要,而是说在项目进行过程中,学习基础知识很多时候没有完成项目的质量保障工作重要。
## 第一次接触WebSocket接口
我在前面说了一大堆方法论,你看到后可能还是摸不到头脑,那么现在,我就以一个我亲身经历的例子来告诉你,面对一个陌生协议接口要怎么去做测试。
大概是在2017年我第一次接触到WebSocket协议的接口当开发工程师告诉我这是一个WebSocket的接口时我一脸懵完全不知道要如何开始测试它。
我先做的就是和开发要了他们调用方的代码当我第一次看到这个代码时还是很难为情的因为它是用Node.js编写的当时我对这个技术知之甚少。但凭着自己的经验积累我多多少少还能看懂一点这个代码然而我在读了代码之后发现自己基于这个代码写测试用例并不容易因为我对Node.js技术实在太陌生了陌生到我无法利用它来完成接口测试。
这种情况我相信你肯定也遇见过那就是开发工程师很Nice地把代码给了你但你却没办法利用它。但这里我想告诉你的是面对一个陌生协议的接口测试任务时无论如何第一次你还是需要先拿到并了解开发工程师写的客户端代码因为这样你就可以对调用方式、参数等接口相关的一些内容有初步印象。在读完相关代码后你就算是和这些接口完成了首次“会面”下面你就要想办法敲开接口的大门让自己能访问被测接口。
由于技术栈问题我没办法借助开发工程师的力量完成接口测试任务因此我接下来想到的是借助一些自己已经熟悉的工具来完成本次测试。我第一个想到的就是我们在之前课程中一起使用过的Fiddler因为在任何一个接口项目开始时无论开发是不是给了我接口文档我都会先用Fiddler访问看一下。
那么WebSocket用Fiddler怎么搞定我当时搜索了一下还真是有办法具体的办法我就不在这里多说了其实主要就是修改了Fiddler中Rules下的Customize Rules如果你感兴趣可以自己去搜一下。我只是想告诉你当你面对陌生技术问题的时候你应该使用你最熟悉的技术去尝试解决问题。
但从下面的图中你可以看到虽然我找到了Fiddler截获WebSocket接口的办法却不难发现所截获的全部消息都在日志里面根本无法操作所以我想用Fiddler完成WebSocket测试的想法也就胎死腹中了。
<img src="https://static001.geekbang.org/resource/image/28/c5/28455a0b055a49b69f30963c1d3cf8c5.png" alt="">
但是我可以借助Fiddler分析WebSocket的接口这也和我们一开始给Fiddler这款工具的定位一样那就是通过它辅助分析我们的被测接口。
## 自己写WebSocket测试代码
当用已有工具基础解决WebSocket接口测试这个想法破灭了后我开始寻求通过编写代码解决WebSocket的接口测试。在这里我还是建议你要以你自己的技术栈为出发点寻找解决问题的方法。由于我的主要编程语言是Python因此下面一些讲解的示例代码段我还是以Python为例但是你要知道解决问题的思路并不限于Python的编程语言它可以是你使用的任何其它语言。
我发现Python提供了WebSocket的协议库因此我只要用它完成客户端的撰写就可以进行接口测试了。这里我写下了第一个WebSocket的调用代码这里我们以 [http://www.websocket.org/demos/echo/](http://www.websocket.org/demos/echo/) 为例),如下面图中所示,我在代码里面写了详细的注释,你肯定能看懂每一句话的意思。
```
#引入websocket的create_connection类
from websocket import create_connection
# 建立和WebSocket接口的链接
ws = create_connection(&quot;ws://echo.websocket.org&quot;)
# 打印日子
print(&quot;发送 'Hello, World'...&quot;)
# 发送HelloWorld
ws.send(&quot;Hello, World&quot;)
# 将WebSocket的返回值存储result变量
result = ws.recv()
# 打印返回的result
print(&quot;返回&quot;+result)
# 关闭WebSocket链接
ws.close()
```
不知道你发现没有上面的代码和HTTP协议的接口类似都是先和一个请求建立连接然后发送信息。它们的区别是WebSocket是一个长连接因此需要人为的建立连接然后再关闭链接而HTTP却并不需要进行这一操作。
我相信你肯定还记得在测试框架那一节([04](https://time.geekbang.org/column/article/195483)我们学习了一些线性的接口测试代码然后通过分析这些代码抽象出Common类随着Common类的不断丰富就形成了你自己私有化的测试框架那么现在问题来了Common类中可以也放入WebSocket的通用方法吗
## 将WebSocket接口封装进你的框架
看见上面的代码我们的第一反应应该是这里有什么东西可以放到我们自己的Common类中呢你可以按照“测试代码即框架”这一思路将这个WebSocket接口封装进你的框架。
我们在前面课程中封装了Common类你可以在它的构造函数中添加一个API类型的参数以便于知道自己要做的是什么协议的接口其中http代表HTTP协议接口ws代表WebSocket协议接口。由于WebSocket是一个长连接我们在Common类析构函数中添加了关闭ws链接的代码以释放WebSocket长连接。依据前面的交互流程实现代码如下所示
```
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# python代码中引入requests库引入后才可以在你的代码中使用对应的类以及成员函数
import requests
from websocket import create_connection
# 定义一个common的类它的父类是object
class Common(object):
# common的构造函数
def __init__(self,url_root,api_type):
'''
:param api_type:接口类似当前支持http、wshttp就是HTTP协议ws是WebSocket协议
:param url_root: 被测系统的根路由
'''
if api_type=='ws':
self.ws = create_connection(url_root)
elif api_type=='http':
self.ws='null'
self.url_root = url_root
# ws协议的消息发送
def send(self,params):
'''
:param params: websocket接口的参数
:return: 访问接口的返回值
'''
self.ws.send(params)
res = self.ws.recv()
return res
# common类的析构函数清理没有用的资源
def __del__(self):
'''
:return:
'''
if self.ws!='null&quot;:
self.ws.close()
def get(self, uri, params=None):
'''
封装你自己的get请求uri是访问路由params是get请求的参数如果没有默认为空
:param uri: 访问路由
:param params: 传递参数string类型默认为None
:return: 此次访问的response
'''
# 拼凑访问地址
if params is not None:
url = self.url_root + uri + params
else:
url = self.url_root + uri
# 通过get请求访问对应地址
res = requests.get(url)
# 返回request的Response结果类型为requests的Response类型
return res
def post(self, uri, params=None):
'''
封装你自己的post方法uri是访问路由params是post请求需要传递的参数如果没有参数这里为空
:param uri: 访问路由
:param params: 传递参数string类型默认为None
:return: 此次访问的response
'''
# 拼凑访问地址
url = self.url_root + uri
if params is not None:
# 如果有参数那么通过post方式访问对应的url并将参数赋值给requests.post默认参数data
# 返回request的Response结果类型为requests的Response类型
res = requests.post(url, data=params)
else:
# 如果无参数,访问方式如下
# 返回request的Response结果类型为requests的Response类型
res = requests.post(url)
return res
def put(self,uri,params=None):
'''
封装你自己的put方法uri是访问路由params是put请求需要传递的参数如果没有参数这里为空
:param uri: 访问路由
:param params: 传递参数string类型默认为None
:return: 此次访问的response
'''
url = self.url_root+uri
if params is not None:
# 如果有参数那么通过put方式访问对应的url并将参数赋值给requests.put默认参数data
# 返回request的Response结果类型为requests的Response类型
res = requests.put(url, data=params)
else:
# 如果无参数,访问方式如下
# 返回request的Response结果类型为requests的Response类型
res = requests.put(url)
return res
def delete(self,uri,params=None):
'''
封装你自己的delete方法uri是访问路由params是delete请求需要传递的参数如果没有参数这里为空
:param uri: 访问路由
:param params: 传递参数string类型默认为None
:return: 此次访问的response
'''
url = self.url_root + uri
if params is not None:
# 如果有参数那么通过put方式访问对应的url并将参数赋值给requests.put默认参数data
# 返回request的Response结果类型为requests的Response类型
res = requests.delete(url, data=params)
else:
# 如果无参数,访问方式如下
# 返回request的Response结果类型为requests的Response类型
res = requests.put(url)
return res
```
上面的代码很长但我的注释很详细我并不建议你一字不落地都看完你只要在使用的时候看一下对应的方法就好了。它是一个超级工具集合最后会变成你自己的类似哆啦A梦的万能口袋你只要做好自己的积累就可以了。
那么使用上述的Common类将上面那个流水账一样的脚本进行改造后就得出了下面这段代码
```
from common import Common
# 建立和WebSocket接口的链接
con = Common('ws://echo.websocket.org','ws')
# 获取返回结果
result = con.send('Hello, World...')
#打印日志
print(result)
#释放WebSocket的长连接
del con
```
现在从改造后的代码中你是不是更能体会到框架的魅力了它能让代码变得更加简洁和易读将WebSocket的协议封装到你的框架后你就拥有了一个既包含HTTP协议又包含WebSocket协议的接口测试框架了随着你不断地积累新协议你的框架会越来越强大你自己的秘密武器库也会不断扩充随着你对它的不断完善它会让你的接口测试工作越来越简单越来越快速。
## 总结
美好的时光过得都很快又到了本节课结束的时候了我今天主要讲了面对一个陌生协议时比如说WebSocket你该如何从零开始完成接口测试任务。
针对一个陌生协议的第一次接口测试,你要保持自己敏锐的测试嗅觉,依据自己的技术基础,尽快解决问题。总地来说,你可以通过三步快速完成测试任务:
1. 借力开发工程师。你首先该借力就是开发工程师,但你不要进入开发工程师给你的那种,从技术基础和理论开始学起,再逐步应用的学习脉络。你要一击致命,直接把他的客户端代码拿来,尽最大可能挪为己用,将其变成自己的接口测试代码。
1. 站在自己的技术栈之上,完成技术积累。如果开发工程师的代码并不能拿来使用,那么你就需要站在自己的技术栈上寻求解决方法,这其中既包含了你已经熟悉的测试工具、测试平台,也包含了自己的测试框架和编码基础。
1. 归入框架。无论你使用哪一种方法,在完成测试工作后,你还是要掌握对应的理论基础,同时想办法将这个一开始陌生的接口,通过自己熟悉的方式合并到你自己的框架中,不断扩充自己框架的测试能力,不断丰富你自己的测试手段。
## 思考题
我们今天一起学习了如何破解陌生协议接口测试难题的步骤那么面对WebSocket的接口测试任务结合你现有的技术栈你是不是也有你自己的解决方案呢你工作中如果有类似的陌生协议既可以是第一次接触的协议也可以是企业私有协议你是如何解决的呢欢迎你在留言区中留下你的疑问和你的做法。
我是陈磊,欢迎你在留言区留言分享你的观点,如果这篇文章让你有新的启发,也欢迎你把文章分享给你的朋友,我们一起沟通探讨。

View File

@@ -0,0 +1,233 @@
<audio id="audio" title="08 | 测试数据是不是可以把所有的参数都保存到Excel中" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2d/6e/2d4d9ee7fcc0e9509dc78cfc3c78926e.mp3"></audio>
你好,我是陈磊。
课程到现在,我们已经一起从接口测试思维的训练,走到了接口测试技术的训练,随着学习的不断深入,你应该也有了一个自己的测试框架,虽然这个框架可能还很简陋。但是任何事情不管多晚开始,都好于从未开始,因此学到现在,你已经迈出了接口测试以及其测试技术的第一步。
做任何事情,从零到一都需要莫大的勇气和坚定的决心,在这个过程中,你要将自己挪出舒适区,进入一个陌生的领域,这确实很难。但如果你和我一起走到了这一节课,那么我要恭喜你,你已经完成了接口测试从零到一的转变,后续从一到无穷大,你只需要随着时间去积累经验就可以了。
如果把接口测试比喻成要炒一盘菜的话,那么我在之前的全部课程中,重点都是在讲解如何完成接口测试,也就是教你如何炒菜。我也教过你如何解决接口测试的需求,为你提供了解决问题的能力和手段,这也就是在帮你建造一个设备齐全的厨房,帮你一起完成接口测试任务 。
有了精致的厨房后,我也告诉了你要怎么制作顶级的厨具,也就是接口测试的技术方法和实践方式。这些厨具既有锅碗瓢盆,也有刀勺铲叉,这里的锅碗瓢盆就是你的测试框架,刀勺铲叉就是你使用框架完成的测试脚本,这其中既包含了单接口的测试脚本,也包含了业务逻辑多接口测试脚本。
那么如果想炒菜你还需要准备什么呢?毫无疑问那就是菜,所谓“巧妇难为无米之炊”,即使你有高超的手艺,有世界顶级的厨具,但如果没有做菜的原材料,那也没办法把菜做出来,就算是世界顶级大厨,也无法完成这样的任务。
今天我就顺着这个思路,和你讲讲菜的准备,也就是接口测试的数据准备工作。
## 测试数据的好处:打造自动化测试框架
随着你不断封装自己的测试框架,你的框架就始终处于等米下锅这样一种的状态,而米就是测试数据。我在之前的课程中,都是将测试数据直接写在代码里,赋值给一个变量,然后通过接口测试逻辑来完成测试。
说到这,我还是把我们之前用过的”战场“这个系统拿出来,看一看它“选择武器”这个接口测试脚本(你可以回到[04](https://time.geekbang.org/column/article/195483)中查看),虽然你现在对怎么撰写、怎么封装类似的接口脚本都已经烂熟于心,但我们还是先看一下它的代码段:
```
# uri_login存储战场的选择武器
uri_selectEq = '/selectEq'
# 武器编号变量存储用户名参数
equipmentid = '10003'
# 拼凑body的参数
payload = 'equipmentid=' + equipmentid
response_selectEq = comm.post(uri_selectEq,params=payload)
print('Response内容' + response_selectEq.text)
```
这就是你通过自己的Common类改造后的测试框架但是现在它还不能是算是一个完美的框架为什么呢
这是因为你现在的参数都是直接通过equipmentid变量赋值的在做测试的时候你还需要不断修改这个参数的赋值才能完成接口的入参测试这不是一种自动化的测试思路。
因此,你需要将数据封装,通过一种更好的方式,将数据存储到一种数据存储文件中,这样代码就可以自行查找对应的参数,然后调取测试框架执行测试流程,接着再通过自动比对返回预期,检验测试结果是否正确。
这样做有两个好处。
1. 无人值守,节省时间和精力。我们将所有的参数都存储到外部存储文件中,测试框架就可以自行选择第一个参数进行测试,在完成第一个测试之后,它也就可以自行选择下一个参数,整个执行过程是不需要人参与的。否则的话,我们每复制一组参数,就要执行一次脚本,然后再人工替换一次参数,再执行一次脚本,这个过程耗时费力,而且又是一个纯人工控制的没什么技术含量的活动。
1. 自动检测返回值,提高测试效率。如果你用上面的代码段完成接口测试,就要每执行一次,人工去观察一次,看接口的返回是不是和预期一致,人工来做这些事情,不只非常耗费时间,效率也很低下。但是通过代码完成一些关键匹配却很容易,这可以大大提高测试效率,快速完成交付。
怎么样,看到这些好处,你是不是也想马上给你的框架加上数据处理的部分了呢?
## 如何选取测试数据
现在我们就马上开始动手,为你的框架加上参数类。
首先,你先要定义一种参数的存储格式。那么我想问你的是,要是让你选择把数据储存在一个文件中,你会选择什么格式的文件呢?
我相信你肯定和我的选择一样用Excel。因为目前来看Excel是在设计测试用例方面使用最多的一个工具那么我们也就可以用Excel作为自己的参数存储文件。
但在动手之前你也应该想到你的参数文件类型不会是一成不变的Excel未来你也有可能使用其他格式的参数文件因此在一开始你还要考虑到参数类的扩展性这样你就不用每多了一种参数文件存储格式就写一个参数类来完成参数的选取和调用了。
那么如何选取和调用参数呢?你可以看看我设计的参数类:
```
import json
import xlrd
class Param(object):
def __init__(self,paramConf='{}'):
self.paramConf = json.loads(paramConf)
def paramRowsCount(self):
pass
def paramColsCount(self):
pass
def paramHeader(self):
pass
def paramAllline(self):
pass
def paramAlllineDict(self):
pass
class XLS(Param):
'''
xls基本格式(如果要把xls中存储的数字按照文本读出来的话,纯数字前要加上英文单引号:
第一行是参数的注释,就是每一行参数是什么
第二行是参数名,参数名和对应模块的po页面的变量名一致
第3~N行是参数
最后一列是预期默认头Exp
'''
def __init__(self, paramConf):
'''
:param paramConf: xls 文件位置(绝对路径)
'''
self.paramConf = paramConf
self.paramfile = self.paramConf['file']
self.data = xlrd.open_workbook(self.paramfile)
self.getParamSheet(self.paramConf['sheet'])
def getParamSheet(self,nsheets):
'''
设定参数所处的sheet
:param nsheets: 参数在第几个sheet中
:return:
'''
self.paramsheet = self.data.sheets()[nsheets]
def getOneline(self,nRow):
'''
返回一行数据
:param nRow: 行数
:return: 一行数据 []
'''
return self.paramsheet.row_values(nRow)
def getOneCol(self,nCol):
'''
返回一列
:param nCol: 列数
:return: 一列数据 []
'''
return self.paramsheet.col_values(nCol)
def paramRowsCount(self):
'''
获取参数文件行数
:return: 参数行数 int
'''
return self.paramsheet.nrows
def paramColsCount(self):
'''
获取参数文件列数(参数个数)
:return: 参数文件列数(参数个数) int
'''
return self.paramsheet.ncols
def paramHeader(self):
'''
获取参数名称
:return: 参数名称[]
'''
return self.getOneline(1)
def paramAlllineDict(self):
'''
获取全部参数
:return: {{}},其中dict的key值是header的值
'''
nCountRows = self.paramRowsCount()
nCountCols = self.paramColsCount()
ParamAllListDict = {}
iRowStep = 2
iColStep = 0
ParamHeader= self.paramHeader()
while iRowStep &lt; nCountRows:
ParamOneLinelist=self.getOneline(iRowStep)
ParamOnelineDict = {}
while iColStep&lt;nCountCols:
ParamOnelineDict[ParamHeader[iColStep]]=ParamOneLinelist[iColStep]
iColStep=iColStep+1
iColStep=0
ParamAllListDict[iRowStep-2]=ParamOnelineDict
iRowStep=iRowStep+1
return ParamAllListDict
def paramAllline(self):
'''
获取全部参数
:return: 全部参数[[]]
'''
nCountRows= self.paramRowsCount()
paramall = []
iRowStep =2
while iRowStep&lt;nCountRows:
paramall.append(self.getOneline(iRowStep))
iRowStep=iRowStep+1
return paramall
def __getParamCell(self,numberRow,numberCol):
return self.paramsheet.cell_value(numberRow,numberCol)
class ParamFactory(object):
def chooseParam(self,type,paramConf):
map_ = {
'xls': XLS(paramConf)
}
return map_[type
```
上面这个代码看着很多,但你不需要完全看得懂,你只需要知道它解决问题的思路和方法就可以了,**思路就是通过统一抽象,建立一个公共处理数据的方式。**你可以设计和使用简单工厂类的设计模式,这样如果多一种参数存储类型,再添加一个对应的处理类就可以了,这很便于你做快速扩展,也可以一劳永逸地提供统一数据的处理模式。
如果你的技术栈和我不一样那么你只需要搜索一下你自己技术栈所对应的简单工厂类设计模式然后照猫画虎地把上面的逻辑实现一下就可以了。接下来你就可以把这次测试的全部参数都存到Excel里面了具体内容如下图所示
<img src="https://static001.geekbang.org/resource/image/93/5c/93da46d5d04c57a87f0cb6fe38583d5c.jpg" alt="">
通过上面的参数类你可以看出在这个Excel文件中第一行是给人读取的每一列参数的注释而所有的Excel都是从第二行开始读取的第二行是参数名和固定的表示预期结果的exp。现在我们使用ParamFactory类再配合上面的这个Excel就可以完成”战场“系统“选择武器”接口的改造了如下面这段代码所示
```
#引入Common、ParamFactory类
from common import Common
from param import ParamFactory
import os
# uri_login存储战场的选择武器
uri_selectEq = '/selectEq'
comm = Common('http://127.0.0.1:12356',api_type='http')
# 武器编号变量存储武器编号,并且验证返回时是否有参数设计预期结果
# 获取当前路径绝对值
curPath = os.path.abspath('.')
# 定义存储参数的excel文件路径
searchparamfile = curPath+'/equipmentid_param.xls'
# 调用参数类完成参数读取返回是一个字典包含全部的excel数据除去excel的第一行表头说明
searchparam_dict = ParamFactory().chooseParam('xls',{'file':searchparamfile,'sheet':0}).paramAlllineDict()
i=0
while i&lt;len(searchparam_dict):
# 读取通过参数类获取的第i行的参数
payload = 'equipmentid=' + searchparam_dict[i]['equipmentid']
# 读取通过参数类获取的第i行的预期
exp=searchparam_dict[i]['exp']
# 进行接口测试
response_selectEq = comm.post(uri_selectEq,params=payload)
# 打印返回结果
print('Response内容' + response_selectEq.text)
# 读取下一行excel中的数据
i=i+1
```
这样再执行你的测试脚本你就可以看到数据文件中的三条数据已经都会顺序的自动执行了。那么后续如果将它付诸于你自己的技术栈以及自己的测试驱动框架比如Python的[unittest](https://docs.python.org/zh-cn/3/library/unittest.html)、Java的[Junit](https://junit.org/junit5/)等,你就可以通过断言完成预期结果的自动验证了。
## 总结
今天我们接口测试数据准备的内容就到这里了,在接口测试的工作中,作为“巧妇”的测试工程师,还是需要参数这个“米”来下锅的,虽然我们之前课程中的代码涉及到参数的处理,但是都很简单粗暴,一点也不适合自动化的处理方式,因此今天,我带你完成了参数类的封装。
有的时候我们也把参数类叫做参数池这也就是说参数是存放在一个池子中那我们准备好的池子就是Excel。我相信未来你也会不断扩展自己参数池的种类这有可能是由于测试接口的特殊需求也有可能是由于团队技术栈的要求。因此我们封装参数池是通过简单工厂设计模式来实现的如果你的代码基础并不好那么你可以不用搞清楚简单工厂设计模式是什么只需要知道如何模拟上述代码再进行扩展就可以了。
一个好用的测试框架既要有很好的可用性,也要有很好的扩展性设计,这样我们的私有接口测试武器仓库就会变成可以不断扩展的、保持统一使用方法的武器仓库,这样才能让你或者你的团队在面对各种各样的测试任务时,既可以快速适应不同接口测试的需求,又不需要增加学习的成本。
## 思考题
今天我们一起学习了参数类的设计,并且将它应用到”战场“系统的接口测试脚本中,后续我又告诉你为了能够完成代码的自动验证,你需要引入一些测试驱动框架,那么,你的技术栈是什么?你在你的框架中选取的测试驱动框架又是什么呢?你能将之前”战场“系统的全流程测试脚本通过参数类完成改造吗?我期待看到你的测试脚本。
我是陈磊,欢迎你在留言区留言分享你的观点,如果这篇文章让你有新的启发,也欢迎你把文章分享给你的朋友,我们一起沟通探讨。

View File

@@ -0,0 +1,83 @@
<audio id="audio" title="09 | 微服务接口怎么用Mock解决混乱的调用关系" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ec/88/ec6a8f90910428d7c0691858d69eb188.mp3"></audio>
你好,我是陈磊。
欢迎你继续和我一起学习接口测试,到目前为止,我们已经学习了接口测试的逻辑模拟,也就是测试辅助工具和测试脚本代码,也学习了如何选取和通过代码调用测试参数,掌握了这些内容,你就算是一个接口测试的老手了。无论你的被测接口是一个你熟悉的协议,还是一个陌生的协议,它们都不会耽误你的工作进度了。
这节课是我们专栏的最后一节课,我想给你讲一讲关于微服务的接口测试。
现如今在我的工作中我主要面对的就是微服务测试每个服务都是RESTful接口。在最开始的微服务改造过程中我的测试其实比之前的业务测试更容易每一个接口通过测试框架来编写测试脚本就可以完成执行了而且一次写完后再通过平台调用也显得很轻松。但是这种美好的场景并没有持续多久。为什么呢 你先听听我的故事。
## 微服务下混乱的调用关系
开发团队开始采用微服务架构开发系统的时候我的测试团队也开始同步学习对应的测试技术我也像从前一样逐步封装自己的测试框架并且采用Postman和Python代码完成接口测试脚本的快速积累同时引入了参数类完成了Excel参数的封装调用。
在开始的一些项目中,只要开发工程师提交了代码仓库主干的合并请求后,除去代码的静态扫描外,持续集成平台会自动调取一个开源的智能化单元测试框架,来完成单元测试,通过后它会自动部署被测系统,然后再执行测试脚本,这整个流程全部是流水线自动驱动完成的。
一般来说开发工程师在开发前期就已经定义好了微服务接口测试工程师和开发工程师几乎是同步开始进行各自的开发任务。但是这种和谐的工作场景很快就被蜘蛛网一样的微服务调用关系给破坏了几乎所有的项目都会出现相互依赖的关系比如说服务A依赖服务B服务B依赖服务C如下图所示
<img src="https://static001.geekbang.org/resource/image/aa/bd/aabd0247cbd7c358443ded723d7114bd.jpg" alt="">
这种混乱主要体现在:
- 当持续集成流水线部署服务A的时候由于对应的开发工程师团队也在做同步改造导致测试环境的服务B不可用
<img src="https://static001.geekbang.org/resource/image/9c/28/9c1e3f58863468fe261553c2dfbe5628.jpg" alt="">
- 由于服务B依赖服务C而服务C还没有开发完成导致即使服务A和服务B都没问题但也没有办法完成服务A的接口测试。
<img src="https://static001.geekbang.org/resource/image/fb/90/fbcb82104f467b3968fe05ec3d9c6090.jpg" alt="">
其实这种服务A依赖服务B服务B依赖服务C的依赖方式还算简单还有更多微服务随着开发越来越复杂服务之间的调用关系就像蜘蛛网一样错乱让你摸不清外部依赖到底有几层以及一个接口到底依赖了几个外部接口。
这就导致了虽然被测系统已经开发完成,测试脚本也准备就绪,但是测试工作就是没办法进行的悲惨结局。面对这种局面,我当时心里确实很不舒服,因为自己做了那么多努力,到头来却被一个不是由自己负责的服务卡住了工作进度,这感觉就像是用尽了全身的力气,却一拳打到了棉花上,自己有再多的劲儿也没处使。
## Mock框架的抉择用什么实现服务B的替身
那作为测试工程师,面对这样的情形,我们该怎么办呢?
我当时想到的就是使用Mock服务。其实Mock服务是一个错误的说法关于这一点我推荐你看一下Martin Flower的这篇叫做[TestDouble](https://martinfowler.com/bliki/TestDouble.html)的文章一般我们将TestDouble服务叫做测试替身但是如今的国内业界里绝大部分人已经习惯了叫Mock服务因此在这里我们也还是叫Mock服务。
针对混乱的调用关系我的思路是我的被测服务就是服务A那么我不用管服务B是不是好用我只要保障服务A能够走完流程就可以完成接口测试任务了。循着这个思路我只要用Mock服务伪装成服务B就万事大吉了我也不用再关心服务B到底调用了多少服务。
但是在选取Mock服务框架时我又面临着一个抉择那就是用什么来实现服务B的替身。现在可以实现Mock服务的框架特别多但绝大部分都要求你有很好的代码基础每做一个Mock服务其实就是做了一个简单的服务B不同的是它不需要实现原有服务B负载的处理逻辑只要能按服务B的处理逻辑给出对应返回就可以了。
因此有些团队也会把这样的服务叫做挡板系统这个名字很形象。也就是说我给了Mock服务B的请求参数它只要按照约定好的返回给我参数就可以了至于一系列其它验证或者微服务调用都不在Mock服务的设计内这就像你对着墙打乒乓球一样墙是你假设的对手会把你打过去的球挡回来而你要做的就是接住墙挡回给你的球。
那么到底应该怎么选择Mock服务框架呢
首先你要基于自己和团队的技术栈来做选择这决定了你完成服务B替身的速度。你要知道无论服务B的替身做得多么完美它只是一个Mock它存在的意义就是帮助你快速完成服务A的接口测试工作因此选择一个学习成本低、上手快并且完全适合你自己技术栈的Mock框架能让你的测试工作事半功倍。
其次你要让写好的Mock服务容易修改和维护。Mock服务就是一个在测试过程中替代服务B的替身就和拍电影时的替身演员一样替身演员可能有好几个需要在不同地方拍摄不同的电影片段。Mock服务可能只有一个也有可能有好几个为了不同的调用或者测试而存在。但是Mock服务会随着服务B的变化而变化如果服务B的请求参数和返回参数有变化那么Mock服务也要能快速完成修改并且能马上发挥作用。因此一个非常容易维护的Mock服务框架才更能马上快速投入使用快速发挥作用。
如果你的团队技术基础很好开发能力很强那么我建议你用对应语言的Mock框架例如Java语言的[Mockito框架](https://github.com/mockito/mockito)和Python语言的[mock框架](https://pypi.org/project/mock/)。
如果你的团队技术基础相对比较薄弱,那么我推荐你看看[moco](https://github.com/dreamhead/moco)这个框架在开发Mock服务的时候提供了一种不需要任何编程语言的方式你可以通过撰写它约束的Json建立服务并通过命令启动对应的服务这就可以快速开发和启动运行你需要的Mock服务。
更重要的是Json格式的数据文件可以独立完成Mock的服务设计而且Json的学习成本和Python语言相比就如同小学一年级的数学和高中数学之间的难度差距一样就更别说和犹如高等数学的Java语言相比较了。如果你想详细的学习moco可以直接去它在Github上的项目空间那里有详细的使用说明和示例代码。
## 我的Mock服务设计经验
在选择好Mock框架后你就可以酣畅淋漓地完成各个外部依赖服务的解耦工作了但是关于Mock服务我还想告诉你一些我的设计经验。
**首先,简单是第一要素。**无论原服务B处理了多么复杂的业务流程你在设计服务B的Mock服务时只要关心服务B可以处理几种类型的参数组合对应的服务都会返回什么样的参数就可以了。这样你就能快速抓住Mock服务B的设计核心也能快速完成Mock服务B的开发。
**其次处理速度比完美的Mock服务更重要。**一个Mock服务要能按照原服务正确又快速地返回参数你不需要把大量的时间都浪费在Mock服务的调用上它只是用来辅助你完成接口测试的一个手段。你需要让它像打在墙上的乒乓球一样一触到墙面马上就反弹回来而不能把球打出后需要去喝个茶或者坐下休息一会才能收到反弹回来的球。
如果你的Mock服务很耗时你在只有一个两个服务时可能影响还不是很明显但如果你同时有多个Mock服务或者需要用Mock服务完成性能测试的时候这就会变成一个很严重的问题后续会引发强烈的“蝴蝶效应”使得整个被测接口的响应速度越来越慢。因此你要建立一套快速的Mock服务尽最大可能不让Mock服务占据系统的调用时间。
**最后你的Mock服务要能轻量化启动并且容易销毁。**你要时刻注意Mock服务只是一个辅助服务因此任何一个团队都不希望启动一个Mock服务需要等待5分钟或者需要100M的内存。它要能快速启动、容易修改并且方便迁移。既然Mock服务的定位是轻量化的辅助服务那么它也要容易销毁以便你在完成测试后可以快速且便捷地释放它所占据的资源。
## 总结
微服务现在已经铺天盖地而来,尤其在中台化战略的推动下,业务中台服务的依赖关系会越来越复杂,并且随着团队内微服务数量越来越多,每个测试团队面临的被测系统都会是一团乱麻,很容易找不到头绪。
为了解决由于微服务间相互依赖而导致的混乱的系统调用关系我建议你尽快掌握一个Mock服务框架这样可以让你在混乱中理清思路快速进行接口测试交付高质量的项目。
最后我要提醒你的是选择Mock的技术栈与选择测试框架的技术栈还是有些区别的在选择Mock技术栈时你重点要考虑的是学习成本把学习成本降到最低才是选择Mock框架的首要关注点。而且你不只要关注自己的学习成本也要关注你所在团队的学习成本因为现在每个项目都有可能需要Mock服务这个时候就要求每一个项目的测试工程师都具备自己独立建设Mock服务的能力在Mock服务的技术选型上还是要以团队整体的技术栈为基础以自己的技术为参考进行选型。
## 思考题
这节课我讲了在微服务混乱的外部调用下使用Mock外部接口完成被测接口的测试工作文中我也给你推荐了一个快速入门的Mock工具那么你在工作中有没有遇见过被测系统因为外部依赖而不得不阻塞项目进度的时候呢你又是怎么解决的呢
我是陈磊,欢迎你在留言区留言分享你的观点,如果这篇文章让你有新的启发,也欢迎你把文章分享给你的朋友,我们一起沟通探讨。