113-接口测试实战课

开篇词 | 把接口测试这件小事做深、做透

你好，我是陈磊，欢迎来到我的接口测试课。

我曾经在京东中台工作，经历了京东虚拟平台质量团队从业务驱动到技术驱动的转型过程，我也是整个过程的推动者之一。

我对接口测试的认知，是我在从测试工程师做到测试架构师这个过程中逐渐形成的，因此，我想先和你讲讲我的从业经历，跟你分享一下我和接口测试的缘分。

接口测试思维，给我带来技术与能力的跃升

我在 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 有可能会影响所有用户，无论他是使用电脑还是手机访问我们的系统。而接口测试就是为了后端服务测试而生的，它会保证后端服务的质量，避免这种情况出现。

抛开它对项目的影响，单单从它对你自身的影响来看，你会发现，如今进入任何一个招聘网站，随意点开一个测试工程师的招聘要求，接口测试几乎已经成为测试招聘中一项必备的技能了。

所以作为一名测试工程师，掌握接口测试，并能熟练完成接口测试，无疑对你在求职时和工作中都有很多好处，比如：

增加自己的技能，在找工作时获得更多机会；

通过接口自动化完成接口回归测试，让自己的工作更轻松、更高效；

通过持续集成平台调用接口自动化测试，为流水线提供质量保障方法和手段，赋能研发。

这个专栏是如何设计的？

为了更好地理解和学习接口测试，我来和你说下这个课程的设计思路。

我把整个专栏的内容分成了三大模块，分别是初级技能篇、综合技能篇和进阶技能篇。

初级技能篇。在这里我会带你重新认识接口和接口测试，了解接口测试都包含哪些测试活动，教你如何由测试工程师主导、整理接口测试需要的输入条件。我也会带你合成接口测试技术和业务测试知识，让你形成接口测试思维。

综合技能篇。在这里我会教你如何从流水账式的接口测试脚本，一步步抽象出属于自己的接口测试框架。这样随着课程的深入，你会建立一套自己的接口测试框架，也会同时完成从接口测试到接口自动化测试的转变。最后，我还会告诉你一种借力打力的方法，借助工具的优势，弥补测试框架的缺陷。

进阶技能篇。在这里我会教你如何利用测试工程师的思维，开始并顺利完成一个陌生的协议接口的测试任务，同时将它加入到自己的测试框架中，不断提升它的测试能力。测试框架是你强大的武器，框架数据层的封装则是你的弹药库。所以最后我会教你完成外部依赖解耦的方法和思路，保证你能顺利完成测试任务，并不会因为遇见不靠谱的队友而迟迟无法交付。

测试行业是一个技术驱动的行业，而在测试的工作内容中，接口测试又是一项基础技术能力。

我相信在我们一起学习的这段时间里，我会和你一起学习这项基础技能，我会将你带入一个通向更有价值、更有满足感的任务中，让这个过程中赋予你接口测试的思维和技能；当你完成这个专栏的学习后，你也会带走有你个人标签的接口测试框架，这些都会让你这个行业里身价倍增。

纵观接口测试的发展，这一项技术在最近两年才上了快车道，所以现在上车，你并不算晚。最后，欢迎你在留言区写下你的工作经历，以及你对接口测试的看法；也欢迎你先为你自己的接口测试平台起一个名字，并在留言区先立下自己的目标，我们一起在这个专栏，与一群志趣相投小伙伴相互督促，相互鼓励，一起学习进步，我相信在学习完所有课程后，你一定会顺利完成你的目标！

01 | 基础：跳出细节看全局，接口测试到底是在做什么？

你好，我是陈磊。

今天开始，我们就来聊一聊接口测试的那些事儿，这是我们专栏的第一节课，在讲解如何做好接口测试之前，我想先给你讲讲它的必要性，再讲讲什么是接口、什么是接口测试。

接口测试为什么重要？

我相信你一定听说过这样一句话：“测试要尽早介入，测试进行得越早，软件开发的成本就越低，就越能更好地保证软件质量。”

但是如何尽早地进入测试，作为软件测试工程师的你，是不是也没办法说得清楚呢？其实上面那句话中的“测试”，所指的并不是测试工程师这个人，而是指包含了单元测试、接口测试、界面测试等一系列质量保障活动的测试工作。

说到单元测试、接口测试和界面测试，你是不是马上就想到了“测试金字塔模型”呢？

在这个金字塔模型中，界面测试、接口测试和单元测试，每一个阶段所占面积的大小，代表了它们在测试过程中的投入和工作量占比。

你可以看到，单元测试在测试过程中，占据了绝大部分的比重，这表示单元测试需要你投入更多的时间和人力成本。但是，单元测试并非测试工程师的本职工作，它属于开发工程师的工作范筹。说到这你可能会问了：“如果开发工程师从来不写单元测试怎么办？”毕竟大部分开发人员都不爱写测试。

其实，我也会问自己这个问题。不可否认，开发工程师不只很少写单元测试，更很少写出好的单元测试代码，很多时候，工期的压力让他们放弃了单元测试。但是，一个产品的交付质量更多时候却是由测试工程师来保障的，面对这一实际现状，我们又该怎么办好呢？

我们聪明的测试工程师提供了两种解决手段：一种是用一些智能化框架补充单元测试工作（如果你对智能化单元测试感兴趣，可以参考我在 2019 年 TiD 上的演讲内容“自动的自动化测试智能化一站式 API 测试服务”）；另外一种，就是加大我们自己主导的接口测试的工作投入比重，来弥补单元测试的不足，这样，上面那个金字塔模型就会逐渐演变成菱形模型。

那之所以出现从“金字塔模型”到“棱形模型”这种变化，并不是有人刻意提高测试工程师在整个交付流程中的地位，这其实是随着工作的不断进行，逐渐形成的结果。

在质量保障过程中，我们的测试工程师会不断增大接口测试的测试深度和测试广度，往下逐渐覆盖一些公共接口的单元测试内容，往上则逐渐覆盖应该由 UI 层保障的业务逻辑测试，这么做的主要目的，就是为了更好地完成质量保障工作，交付一个可靠的、高质量的项目。

所以，从接口测试这一环节开始，测试工程师就变成了质量保障工作的主要推动者，接口测试也变得愈发重要。那它有什么好处和优越性呢？我觉得可以从下面这 3 个角度来看：

接口测试更容易和其他自动化系统相结合；

相对于界面测试，接口测试可以更早开始，也可以测试一些界面测试无法测试的范围，因此它使“测试更早的投入”这句话变成现实；

接口测试还可以保障系统的鲁棒性，使得被测系统更健壮。

现在，我相信你已经意识到接口测试在质量保障中的重要地位了，那么，你知道究竟什么是接口吗？接口测试又在测些什么呢？我们又为什么要做接口测试呢？

下面我就逐一把这些讲解给你。

接口是什么？

如果你想要知道接口测试在测什么，首先就要知道接口是什么。

在这里我不想告诉你书本上是怎么定义接口的，从那些晦涩的语言中，你可能读几次都不能真正理解它的含义，我准备用一个实际生活中的例子，来告诉你接口究竟是什么。

我相信你肯定去过麦当劳，那每次在你去麦当劳吃东西时，你是否细心观察过它为你准备订单商品的过程呢？

如果你的订单上有一个汉堡，工作人员会先找到汉堡的原材料如面包片、肉饼和生菜等，按照规定步骤，将这些原材料组合成一个汉堡，然后送给你；如果你的订单上有一份薯条，那么工作人员会进入另外一个工作流程，先找到薯条原材料和炸薯条的锅，把薯条炸好后，送到你面前。

那么在上面的例子中，汉堡以及薯条的原材料就是接口中必要的条件入参，也就是接口的特定输入；制作汉堡或烹饪薯条的过程，就是接口内部的处理逻辑；送到你面前的汉堡和薯条，就是接口的处理结果和特定输出，也就是返回参数。

所以我们可以看到，接口就是有特定输入和特定输出的一套逻辑处理单元，而它不用知道自身的内部实现逻辑，这也可以叫做接口的黑盒处理逻辑。

而从上面的例子你也可以看到，由于服务对象不同，接口又可以分为两种，一种是系统或服务的内部接口，一种是外部依赖接口。

内部接口

简单来说，内部接口就是系统内部调用的接口。

在上面麦当劳的例子中，内部接口有两个：

汉堡订单。服务员在接到订单后，输入汉堡的原材料，将汉堡做好后，放到后厨和前台之间的一个中间储存柜里，作为输出，为下一个中间储物柜接口提供输入参数。

中间储物柜。服务员从中间储物柜拿出汉堡，这就是这个内部接口的特定输入，最后送到你面前的汉堡，就是这个内部接口的特定输出。

那么在软件系统中，内部接口是怎么一回事呢？

其实，你在网上购物时，要先登录系统，然后将商品加入购物车，再接下来支付订单。那么，从添加商品到购物车，再到支付订单，这一长串的流程之间，就是通过系统内部接口来完成的。

外部接口

刚刚说了内部接口，那什么是外部接口呢？其实它是相对于内部接口而存在的一个概念，上面你在麦当劳点餐的场景就是一个外部接口，它又可以分为两部分：

出订单前，你的点餐过程。这个外部接口特定的输入是你在点餐时，告诉服务员你想点什么，这也是你输出给麦当劳的参数。

出订单后，服务员送餐的过程。它的特定的输出是服务员把汉堡送给你，这也是麦当劳返回给你的处理结果参数。

在系统上，外部接口又是怎么回事呢？

你在购物后点击付款时，页面会跳转到支付系统，等你完成支付流程后，再跳转回订单页，在这样的流程中，都会涉及系统对外的接口，还有，比如说付款工程的支付接口、配送过程的物流接口等等。

现在我来总结一下接口的本质，它其实就是一种契约，遵循这样一种形式：在开发前期，我们约定接口会接收什么数据；在处理完成后，它又会返回什么数据。

如果调用方和被调用方都遵从了这种契约，那么就可以达到共同开发的目的，开发完成后，联调完成系统逻辑的前期预期，提高研发效能。

什么是接口测试？

还是以麦当劳的汉堡为例，接口测试，其实就是要验证制作汉堡的过程是否正确。这里所说的“正确”其实有两方面的意思：

一方面，是要验证输入了汉堡的原材料，经过制作汉堡的处理流程，最后交付给你的是一个汉堡；

另外一方面，是要验证在输入的汉堡原材料不对或者不全的情况下，经过制作汉堡的处理流程后，不能给你交付一个汉堡。

你一定要注意，这两方面的验证是都要进行的，对于一个测试工程师来说，这两种流程都是正向流程。只有理解了这个思维，你才能把自己从客户思维里拉出来，形成测试工程师思维。

我相信你在工作中，已经接触过各式各样的接口了，比如说 HTTP 协议的接口、RESTful 格式的接口、WebService 的接口、RPC 协议的接口等。其实无论是哪一种形式的接口，它们都是通过某一种传输协议，在 Client 端和 Server 端之间来完成数据传递的。

假如你现在测试的，是 Web 端的极客时间，那么 Client 端就是浏览器，Server 端就是 Web 服务，那么浏览器和 Web 服务之间，就是通过 HTTP 协议传输的；

如果你测试的，是移动端的极客时间，那么 Client 端就是你的设备上安装的极客时间应用，Server 端就是 RESTful 格式的接口服务，那么极客时间的应用和 RESTful 格式的接口服务，就是通过 JSON 格式的数据来传递的。

看到这，我想你也能理解了，接口测试其实就是模拟调用方，比如 Client 端，通过接口通信来检测被测接口的正确性和容错性。

但是请你放心，我现在所说的模拟调用方，并不是让你开发一个浏览器或者极客时间的手机应用，而是让你模拟这些客户端上的前端逻辑，调用 Server 端提供的接口，你完全可以借助一些工具或代码来完成这项工作。

这些相关的工具或代码技巧，也就是我设计这个专栏的主要思路。但我不想把这些工具一次都罗列给你，让你马上就失去兴趣，这是由于两个原因：

当你看到一个好几页的工具列表或者技术列表式时，你可能会觉得，自己需要翻越无数个高峰才能学会接口测试；

另一方面我也觉得，在接口测试中，工具或代码并不是它的核心内容，接口测试思维才是你应该重点关注的问题。

所以，我会在做一些工作或者任务的过程中，把这些工具介绍给你，让你知道哪些工具能够解决哪些问题，用什么样的代码可以解决什么样的问题。我也更希望你知道，工具和代码并不是相互排斥的，而是相互依存和相互辅助的。

其实，接口测试和你以前最熟悉的业务测试一样，都是关注输入和预期是否一致，尤其是输入数据中有一些非法输入的时候，接口的处理和逻辑控制是否合理，这些都是通过返回值来判定的。

还有一些小概率逻辑的处理也是我们设计输入的关注重点，比如一些代码中的异常情况，我们也要想办法，通过输入参数来触发这种逻辑分支，通过返回值来判定对应接口内部实现的处理逻辑是否合理、是否健壮。

这样看来，接口测试对于你来说，也不是一个全新的工作内容，但它还有自身的特别之处的，比如说：

在测试手段上，接口测试算是技术驱动和业务驱动双管齐下的工作（界面测试却是业务驱动为主的工作），因此，你需要借助一定的工具来完成它。这个工具既有可能是成熟的工具，也有可能是你自己写的代码，因此，测试技术会在接口测试阶段，变得和业务知识一样重要。

在工作范围上，接口测试影响的范围会更广一点，它会覆盖一部分单元测试的内容，也会覆盖一部分业务测试的内容，但是，无论是哪一部分的内容被它侵占，相对应部分的工作投入其实都减少了。

总结

今天我们一起聊了聊接口测试，有人说，从吃的开始聊起就很容易打开谈话的僵局，所以我们从麦当劳的汉堡开始，讲述了接口为什么重要、接口是什么以及接口测试在测些什么。我还讲了接口测试和业务测试的区别和联系，那就是“相互依存，不可分割”。

虽然我今天洋洋洒洒给你讲了很多内容，你也有可能记住的并不多，但我希望下面这三点你一定要烂熟于心：

接口测试是通过设计输入和预期输出来完成测试验证的，你之前掌握的测试用例设计方法等测试基本功，在这里还是有用武之地的；

接口测试是一个技术知识和业务知识相结合的工作，可以更好地提升你自己的技术实力，让那些说我们是“点工”的人早早闭嘴；

接口测试也是功能测试，要说有和界面测试不同的地方，仅仅是和我们交互的，不再是开发工程师设计的界面，而是测试工具或者代码。

在很多人眼里，接口测试是技术，业务测试是业务，但它们其实是不可分割的。所以在这个专栏中，我会通过先介绍方法再引入工具，到最后用代码引入封装框架，一步一步教你完成接口测试。

思考题

我们今天的课程到这里就结束了，现在我想请你思考一下，你还能举出其它实际的例子，证明接口测试是质量保障环境中，必不可少的一个测试阶段吗？

我是陈磊，欢迎你在留言区留言分享你的观点，如果这篇文章让你有新的启发，也欢迎你把文章分享给你的朋友，我们一起来探讨和学习。

02 | 方法论：没有任何文档，怎么才能快速了解接口的信息？

你好，我是陈磊。

我相信在学习完上一节课后，你已经明白了接口测试是在测什么，我们为什么需要做接口测试。那么，当你面对一个接口测试任务的时候，你知道该如何开始吗？其实，任何事情从 0 到 1 都是一个门槛，你只要跨过这个门槛，后面就会一马平川。今天我就来告诉你，如何开始接口测试，让你面对一个项目不再束手无策，也不再面露难色。

说起接口测试，我想你并不陌生。作为一名测试工程师，尤其是做了多年业务测试的测试工程师，在开始接触接口测试时，无论开发工程师是否提供了接口文档，我相信你都会对下面几种场景似曾相识：

开发工程师提交测试的项目，附带着一个几十页的 Word 文档，里面是一行一行的访问地址和路由，面对这样的 Word 文档，不知道如何开始验证；

开发工程师在即时通讯工具上，甩给你有好几页的这么一个传输消息，里面有各种嵌套的参数，你不知道这些参数都是干什么用的；

开发工程师口头告诉你需要测试的接口地址，然后就什么都没再多说，你问了他几句话后，他就借口说自己忙，不再理你，而你看到那个又长、又复杂的地址，束手无策。

难道，面对这些状况，测试工程师就没办法自己分析接口，完成测试吗？我现在告诉你，当然不是。

接下来，我就带你一起看看，一个理想的提测项目是什么样的，在实际工作中，绝大部分的提测项目又是什么样的，然后我们一起看看，如何一步一步解决一个不理想的提测项目。

一个理想的提测项目

一个理想的提测项目，在提测的过程中应该既包含前期参与的产品需求、原型设计，这些是由产品经理来提供的；也包含后端接口文档、代码单元测试脚本，这些是由开发工程师提来供的。这些都是你开展测试的必要输入内容，它们具体有这些作用：

产品需求。它描述了系统的业务逻辑，通过这个文档，你才能知道怎么来设计测试用例；

原型设计。它会更加直观地告诉你系统的使用逻辑，这对测试用例的设计、对系统的前期认知都是有辅助作用的。

接口文档。它详细地描述了后端接口的访问方式和参数说明，使用这个输入项才能开展接口测试用例的设计、测试脚本的准备和测试数据的构建。

单元测试脚本。它是保障提测质量的必要环节，是研发工程师自测的一个有效手段，可以保障提测项目的提测质量。

这些内容不限制 SUT（System Under Test，被测系统）的类型，SUT 既可以是一个手机 App，也可以是一个 Web 服务，甚至还可以是一个微服务接口。

所以，对于接口测试阶段来说，一个非常理想的接口测试，就是从完美的接口文档开始的。开发工程师在设计和开发接口的过程中，就在不断维护和更新接口文档，这其中包含了每一个接口的访问方式、访问路由、输入参数含义、返回参数含义，以及一个完整的例子。

这种接口文档可能是以 Word 文档形式存在，也有可能是以类似 Swagger 这种工具形式存在。说到 Swagger，它是我推荐给你的一个接口文档的存在形式，是一个从代码生成的、以 Web 服务形式存在的接口文档，它可以伴随代码的变更同步变化，这就减少了很多开发工程师和测试工程师之间的沟通成本。

由 Swagger 生成的接口文档的例子如下图所示，从图中你可以看到它对接口的访问方式、访问路由、参数都有着详细的描述。

这样，当你拿到接口文档时，就可以快速使用各种工具或者代码来完成你的单个接口测试任务了。与此同时，你还可以通过一些参数设计、参数上下文传递，来完成接口的流程测试。

理想的情况很难发生

上面我说的是很理想的情况，现实却往往并不总如人意，我相信你也肯定遇见过下面这些情况：

根据产品经理的一句话需求，开发工程师便开始任意发挥，“所见系统即需求”的情况普遍存在，这就更别说后续的单元测试和接口文档了；

开发工程师从来不写单元测试脚本，提测项目质量无法保障，接口文档更无从谈起，你不知道如何开始完成接口测试；

你在拿到提测项目后，从部署测试环境到开始测试，一直都是摸黑前进，由于接口测试没有充分的输入条件，只能从 UI 层开始测试，结果导致交付质量大打折扣。

就像墨菲定律说的那样：可能发生的事情必将会发生。所以，上面我列举的接口测试难以推行的一些常见情况，都是你在工作中会碰见的问题。

那么，如果一个项目没有接口文档，我们就无法开始接口测试了吗？当然不是。

测试工程师的工作本质上是一个由表及里的工作，如果你还是每次工作都处在和终端用户使用行为几乎一致的流程上，那么只能说明你还不算一名合格的测试工程师。

其实，无论开发工程师给我们的输入项是否包含了接口文档，我们都可以通过一些技术手段和工作方法，完成接口测试必要的输入项接口文档的创建。

那么这样的工作如何开始呢？下面我就来和你一起完成这样一项任务，教你如何开始第一个接口测试。

开始第一个接口测试

在拿到一个 SUT 环境的时候，你首先就要进行接口测试，这是因为单元测试不是由测试工程师来完成的，而是由开发工程师编写、并由持续集成系统自动完成执行的。

如果开发工程师没有给我们任何有价值的文档，那么要开始接口测试，你可以通过工具辅助、分析问题、询问解惑这三个步骤来完成。

具体的工作模式如上图所示：

借助一些工具的辅助来完成接口分析；

通过工具截获一些接口信息；

通过分析接口的访问方式、参数等信息整理出一些问题，和研发工程师沟通这些问题，将一些不知道的参数含义、参数取值范围等问题问清楚。

通过这三步的循环，你就可以完成对 SUT 系统接口信息的完善和维护，最终得到一份完整的、接口测试需要的输入—接口文档。

下面我会结合一个案例，带你看看这三步具体该如何进行。

工具辅助

当你第一次拿到一个被测项目，无论它是一个 App 服务还是一个 Web 服务，你都可以通过一些 HTTP 代理完成接口分析，这里我推荐你使用 Fiddler 这款工具。

注意：

Fiddler 既支持 Windows 操作系统，也支持 MacOS 操作系统，但是在 MacOs 上的版本并不好用，这是因为 Fiddler 使用了.Net 开发。如果你是一个 MacOS 深度用户，那么我推荐给你两款工具，一款是 Charles，另外一款是 mitmproxy。其中，Charles 是商业软件，mitmproxy 是开源软件，但是 Charles 使用起来更简单，mitmproxy 的使用则稍微复杂一些，你可以依据自己的喜好来选择。

简单来说，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 请求的内容了。

为方便你查看，我将 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 ”。这里面的具体属性内容，你可以自行查看，但是，我希望你重点关注如下这几个属性：

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 中传递的参数很多都是用来确认用户身份、鉴定角色权限等需要的参数。这样，通过上面的分析你就可以自己绘制如下的表格，这里我也给出了一张表格。

在这个表格中，被标注为白色背景的部分，是这次访问的基本信息；被标注为黄色背景的部分，是访问的头信息，同时也是我们已知的内容；被标注为红色背景的部分，就是 Cookie 信息，是我们未知的内容。同时你可以看到，本次消息访问的 body 是空的，是没有内容的。

接下来我们再看看这个请求的 Response 信息，由于返回的消息比较长，我在这里就不贴出来了，但是通过下面这个图你可以看出，这次返回的主体是一个很长的 JSON，这里面包含了各个专栏或者课程的信息。

这些返回值包含了很多参数，你也需要关注这些参数，因为很多时候，一个接口的返回值会是另外一个接口的入参，也就是我常说的串联业务逻辑上下文的参数。

现在，在你绘制的接口信息表中还有一些未知的 Cookies 内容，就如我前面说的，Cookie 内容是完成接口测试必须要模拟并传递的一些信息，因此，我们必须要尽可能完善它，使它成为接口测试的必要输入条件之一。

这样，拿着这张接口信息表，我们就进入了第三步，询问解惑。

询问解惑

对于本次访问的 Cookies 的参数，从参数语义上来说，我们无法知道这些参数是用来干什么的，他又起到了什么作用，作为测试人员，我们也无法只靠自己知道每一项具体的含义、表示的内容以及参数的作用。

因此，我们需要拿着上面的那张表格，找到对应的开发工程师，去问清楚表格中标红部分的参数。

针对每一个参数，你都要从下面的几点详细询问，并保证你已经真的理解了这些内容。那么，都询问些什么呢？我认为主要有三点。

参数的含义以及来源。你要搞清楚每一个参数的含义，也就是这个参数对应的实际自然语言的名字，通过记录每一个参数的中文语义，也会让更你容易记住这个函数是干什么的。同时，你也要知道这个参数的赋值是从哪里来的，是从其他页面的返回值中得到的？还是 JS 生成的？如果是其他页面或者接口返回的，那么，是哪一个接口返回的哪个字段？这样，当你开始做接口测试的时候，你就知道去哪里拿到这个参数的赋值了。如果是另一个接口的返回字段，那么，你还需要维护一份返回该参数接口的接口信息文档，以便于自己下一次创建对应的参数，如果不可以创建，那么你就要知道这个参数的生成规则，也要知道如何手动构造它。

参数的作用域。参数的作用域指的是这个参数在这个接口中是做什么用的，它在哪一个访问周期里是一直存在的，它是否导致了业务逻辑分支等。比如说，这个参数是用来验证用户权限吗？它的验证算法是什么？之所以要搞清楚这些内容，是为了你在做接口测试的时候，可以设计更小的参数组合来覆盖更多的业务逻辑，这是测试用例去除冗余的一个很好的方法。

返回值的含义。针对上面一大串的返回 JSON，你要搞清楚在返回值中，每一个 JSON 的 Key 所对应的含义，这样，当你需要和这个接口产生交互的时候，就可以快速地拿到对应参数的含义，完成业务逻辑上下文的参数串联了。

总地来说，Request 的全部参数和 Response 的全部参数对于接口测试来说，都是必要的输入项，因此我们有必要花费很多精力完善并且留存它们。

OK，到现在，你已经借助工具、通过分析问题明确了未知参数，也通过询问解决了未知参数的中文含义、作用域，以及对应返回参数的中文含义，现在，即使面对没有接口文档的提测项目，你也能收集明确的、足够的信息了。

那么接下来，你就可以利用这些信息，完成业务逻辑的接口测试了。

多个接口串行分析

在质量保障过程中，测试的主要任务，是保障 SUT 的业务逻辑正确性，而单一接口的测试却很难完成一个业务逻辑，所以，在大部分的测试场景中，我们都需要串行多个接口，才能完成一个完整的业务逻辑。

然而，即使我们按照上述三个步骤完成了全部单个接口的分析，也并不能马上开始进行接口测试。这是因为，一个测试的业务逻辑是由多个接口的串行完成的，而多个接口的串行逻辑是由业务逻辑规定的，因此，多个接口之间并不是随意组合的，而是按照业务逻辑、通过数据传递来完成的。

这其实就和拼图游戏一样，我们有一堆拼图碎片，很多拼图碎片都可以连接到一起，并不会有明显的不适合，但是，依据拼图的最终图形，这些拼图碎片就是不能放到一起。你要想把拼图完成，就不仅要考虑各个拼图碎片是不是可以链接到一起，还要考虑这些碎片放到一起后是不是对原来图形的正确拼接。

那么，你前面整理好的、各个单一接口的信息表，就是拼图游戏里的一个拼图碎片，业务逻辑就是拼图组成的最终图形，而其中的参数，就是拼图碎片的缺口和每一个碎片上的图形。

所以，要想使用接口测试完成业务逻辑，你就要制作一个流程中所有接口的接口信息表，同时，还要理清每一个流程的数据流程，数据流程驱动了业务流处理，这样，才能开始业务逻辑的接口测试。

关于怎么完成一个业务逻辑的接口测试，我会在下一节课中详细的解释给你。

总结

好了，今天的内容就到这里了，不知道你是不是已经掌握了分析一个接口并整理接口信息表的方法呢？现在我来总结一下。

在今天的课程中，我带你一起分析了一个理想的提测项目都包含哪些要素，但是，这种乌托邦式的提测项目在现实中却很难遇见。所以，我给你推荐了“三步走”的方法，你可以通过工具辅助、分析问题和询问解惑这三步来建立接口信息表，建立和维护自己的接口知识库。我也通过拼图游戏的例子，告诉你接口测试的业务逻辑验证思路。

随着被测系统的不断迭代，当你不断地按照这个步骤积累起自己的接口知识库时，你就会拥有自己负责的业务线的业务知识积累，变成这条被测业务线的业务专家；同时你也会拥有每一个接口的输入和输出全部参数、每一个业务逻辑的数据流、每一个驱动业务分支逻辑的参数条件，你也会成为接口测试的技术专家，在你自己的工作中变成一个不可或缺的质量保障者。

在一个团队中，有很多时候，测试工程师和开发工程师之间就是一个矛盾的共同体，既相互依存又不可分割，在团队中缺一不可，要想摆脱“测试非技术岗位”的帽子，就需要测试人员有自己的技术功底和技术素养。

无论你在工作中，遇到的是能很好相互支持的开发工程师，还是不好合作的开发工程师，你都要保持自己的技术能力，尽最大努力完成自己能够完成的所有事情，只有这样，你才能提高自己在团队中的话语权。

思考题

最后，我想请你来思考一下，你能用本节课讲述的 Fiddler 还有接口测试分析方法，完成一个你自己项目的老旧接口的分析吗？

我是陈磊，欢迎你在留言区留言分享你的观点，如果这篇文章让你有新的启发，也欢迎你把文章分享给你的朋友，我们一起沟通探讨。

03 | 思维方式：用一个案例彻底理解接口测试的关键逻辑

你好，我是陈磊。

在前面的课程中，我们聊到了如何开始一个接口测试，我相信你一定掌握了整个过程的推进方法，这包括如何分析一个不理想的提测项目的接口，并在自己的能力范围内完善和维护接口文档，最终设计一个流程化接口测试用例。

你还记得这其中的关键点吗？其实就是：

工具辅助。借助一些工具的辅助来完成接口分析。

分析问题。通过分析接口的访问方式、参数等信息整理出要解决问题。

询问解惑。针对问题和研发工程师进行沟通，把一些不知道的参数含义、参数取值范围等问题沟通清楚。

那么，这些都准备好后，你又如何通过一个实际方法落地接口测试呢？这里面就涉及到怎么做单接口的接口测试，怎么完成业务逻辑接口测试，以及用什么手段来完成接口测试等问题。接下来我会为你详细解答这些问题。

今天，我会带你一起利用Postman这款工具来测一个系统。

简单来说，Postman 就是一个 HTTP 协议客户端工具。但它只是我们完成这次任务的手段，接口测试用例的设计和实现过程才是我今天想告诉你的重点内容，所以，我在这里不会给你讲它的详细使用方法，而是会花更多时间告诉你怎么利用接口测试的思维方式来使用它。你也不用担心，今天我们这节课涉及的 Postman 的功能都很简单，不会因为你没有基础而显得晦涩难懂。

明确被测系统

有了被测系统我们才能开始聊接口测试，但是，目前网络上可以看到的系统例如极客时间的手机应用、百度网站等并不适合做接口测试的讲解，这是因为我们需要知道接口的每一个参数，以及一些接口的处理逻辑。

所以，我给你准备了我自己专门制作的一个小型系统 Battle，它是一个理想的被测系统，你可以在GitHub中下载它的详细系统代码。

我先来简单介绍一下它，它是一个类似回合制的游戏，通过接口测试的方法和服务器发生交互，模拟两个角色进行决斗，最后可以知道到底是谁赢了。详细的说明和代码都在 GitHub 上，你可以自行查看。除了 GitHub 上你可以看到的单个接口的说明以外，还有一个就是业务流程，这个系统的业务流程是：进入系统后，选择武器，然后和你选择的敌人决斗。

开始接口测试

在开始业务逻辑接口测试之前，你要先通过接口测试的方法，测试每一个接口都是正确的，既要保证单接口的正确性，也要保证接口的业务逻辑正确性，这里所说的“正确”指的是“正确接受合法 Request 入参，正确拒绝非法 Request 入参”。

单接口的测试

单接口测试的重点，其实就是保证该接口的正确性和健壮性，也就是说，你既要保证这个接口可以按照需求，正确处理传入的参数，给出正确的返回；也可以按照需求，正确的拒绝传入非正确的参数，给出正确的拒绝性返回。

现在，我就带你一起使用 Postman 来检测单接口的测试。

首先，打开 Postman，你可以看到它的 UI 结构很简单，为了可以检测首页接口，你需要设定 HTTP 的访问方式为 GET，URL 是http://127.0.0.1:12356/，点击发送按钮后，就可以在工具下面的 Body 部分看到接口返回的说明性文本内容了，这个内容是“please input your username(your english name) and password(your english name)”。

对于一个 GET 请求的接口，我们在上面已经完成了单个接口的测试工作。那么接下来，我们就要检测第二个接口：登录接口，它的访问方式是 POST，参数是 username 和 password，这两个参数均不可以为空，也不可以超过 10 个字符；如果 username 和 password 这两个字符串相同，会登录成功并返回后续的说明性文本，否则，就会正确拒绝登录。所以在这一步，我们会多检查一项 Request 的参数设计，用边界值方法设计的参数如下图所示：

在获取了参数后，下面你就要借助 Postman 这个工具，选择 Post 访问方式，输入登录接口的 URL，在 Request 的 Body 中输入 username=criss&password=criss 的参数，然后点击发送，接下来，你就会在 Response 的 Body 中对应返回内容。如下图所示：

按照上面的方法，依次完成剩余两个接口的测试用例设计和测试用执行过程后，你就完成了单个接口的测试工作。

然而完成了这一步，只能算是把接口测试工作完成了一半，另外一半就是要按系统的业务逻辑来完成“正确的流程可以完成处理，不正确的流程可以正确拒绝处理”这个验证。

业务流程接口测试

业务流程接口测试，主要是保障通过多个接口的串联操作可以完成原来需求中提出的业务逻辑，这也是它主要关注的内容。

在前面，我已经告诉过你这个系统的主要业务逻辑：“进入系统后，选择武器，然后和你选择的敌人决斗。”

依据上面这种业务逻辑描述，还不能完成业务流程的接口测试，我们需要对其做进一步的分析和细化。依据这个业务需求，至少有下面这几个业务流程：

正确登录系统后，选择武器，与敌人决斗，杀死了敌人；

正确登录系统后，选择武器，与敌人决斗，被敌人杀死；

正确登录系统后，选择武器，与敌人决斗，两个人同归于尽；

正确登录系统，选择武器，没有选择敌人，自尽而死；

正确登录系统，选择一个未提供的武器编号，选择一个敌人，自尽而死；

正确登录系统，选择武器，选择一个未出战的敌人（不在返回提示列表中），自尽而死。

那么针对这些业务流程，我们设计的参数如下：

接着你就可以利用 Postman，将过程参数手动传递给下一步接口，这样，你就可以建立一个业务流程的接口测试，并最终完成测试了。Postman 也提供了参数化的方法，如果你对此感兴趣，也可以自行学习。

继续按照上面的流程，用 Postman 将上述其它五个流程完成，你就完成自己的接口测试了。如下图：

现在，你已经有五个业务逻辑接口测试用例了，但是通过观察上面的业务流测试，你会不会感觉与你自己平时手动写的业务测试相比，好像少了点什么？

没错，它和业务测试的测试用例相比，确实少了很多异常状况，比如正确登录、正拒绝登录、正确登陆选择的装备参数是字符串等等，这一系的业务流中的反向用例都没有进行验证。

这也是接口测试和业务测试在设计测试和执行测试过程中的差异点，在接口测试中，我们通过单个接口测试完成了全部异常状态的覆盖；而在业务流程中，我们更需要关心业务流和数据流的关系，并不需要再过度关心如何用业务流的方法覆盖更多的代码逻辑异常，这也是分层测试中为什么在单元测试和界面测试之间要加入一层接口测试的主要原因之一。

通过单接口测试，可以更加接近于单元测试；通过业务流的接口测试，可以更加接近于界面所承载的交互中的业务流验证，这也是为什么现在很多人在提倡将测试模型由原来的金字塔形往菱形转变的依据之一了。

而完成了这一系列流程，其实你也就掌握了接口测试的思维：先从单个接口的测试开始，保障单个接口的正确性和健壮性，然后通过单个接口的测试完成多个接口的业务逻辑串联，站在业务逻辑的角度完成业务逻辑的正确性检测。

你的接口测试也可以和持续集成结合到一起

通过 Postman 这个工具完成从单接口测试用例的设计到业务逻辑接口测试用例的设计，你就已经掌握了接口测试的思维以及具体的实现方法。但是到目前为止，你还处在手动测试的阶段，虽然已经和以前基于界面的业务测经有了很大区别，但是距离自动化的接口测试还有一定的差距。不过你不用担心，因为这个差距仅仅是一个工具的距离。

我相信你一定听说过持续集成，在持续集成中，有一个很重要的环节就是要持续测试，通过持续集成平台调取自动化测试，完成质量保障工作。现在你已经有了 Postman，已经完成了基于 Postman 的接口测试脚本，那么如何将其赋能给持续集成平台呢？

这里我们要借助 Newman 这款工具，它就是 shell 下的 Postman，我们将 Postman 的业务逻辑接口测试脚本导出后，push 到本地的 Git 仓库中，持续集成平台就可以通过 pull 对应的接口测试脚本，然后通过 Newman 执行，这样就可以完成持续集成平台的赋能了。

在这里我只提供给你一个思路，具体的完成方式，你可以通过学习持续集成平台 Jenkins 和 Newman 运行 Postman 脚本完成对应的内容。

总结

我相信今天的内容你一定可以挪为己用了，如果你和我一起完成了这些操作，同时在课后，你也弥补了我在课上跳过去没有讲到的一些接口测试脚本，那么我相信你现在肯定被成功的喜悦所围绕了。

走到这一步，你已经掌握了接口测试的思维，在这种思维的指导之下，用什么技术手段或者工具去完成接口测试，也就显得没那么重要了，这也是为什么我并没有将 Postman 这个工具一步一步教你怎么用的原因，因为你既可以选择我推荐给你的 Postman，也可以找到一个你自己喜欢的工具或技术方式完成接口测试。

接口测试的执行方式、设计思维都和业务测试不完全一致，它们既有交集又有差异。交集部分是它们都会涉及到业务逻辑测试，但是接口测试更加关注有数据流驱动的业务流程，而不再着眼于代码异常、代码边界等，这些边界问题在接口测试过程中已经由单接口测试完成了。

接口测试在单接口测试的设计思维上也更加贴近于代码的单元测试，但它还是站在 Client 端的角度来完成测试；而接口测试的业务逻辑测试更加靠近手工业务测试，但却更加聚焦于业务逻辑本身，不再将一些非法业务异常放到该部分进行测试。

思考题

今天我并没有将全部测试用例都用 Postman 完成，那么你自己完成了吗？如果你已经完成了，那么你能用 Newman 驱动执行单接口测试脚本和业务逻辑测试脚本了吗？如果单接口测试中登录测试用例有一条执行失败了，你可以告诉我是哪一个测试用例吗？

我是陈磊，欢迎你在留言区留言分享你的观点，如果这篇文章让你有新的启发，也欢迎你把文章分享给你的朋友，我们一起探讨和学习。

04 | 案例：如何把流程化的测试脚本抽象为测试框架？

你好，我是陈磊。

在上一节课中我和你强调了，用什么工具或代码解决测试问题并不重要，拥有接口测试思维才更重要。在今天的课程中，我会带你从零开始打造一个测试框架，建立你自己的技术体系。

在这里，我推荐你学习一门编程语言，以便你可以更加得心应手、个性化地改造你的测试框架或工具。在这节课中，我会以 Python 语言来展示我的代码示例，不过语言本身不是重点，你只需要了解这其中的逻辑与方法即可，同样的事情，你可以使用 Java、Go 等任何你喜欢的语言来完成。

当然，如果你想学习 Python 语言的话，我推荐你花一个周末的时间看看尹会生老师的“零基础学 Python”课程。

为什么要开发自己的测试框架？

之前，我们说到了用 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 + '&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) > 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 + '&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 + '&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+"&equipmentid="+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’，如果这里这样写，你的 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 + '&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+"&equipmentid="+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) > 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) > 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 协议的接口验证了。

我相信现在你已经掌握了测试框架的形成过程，就如下图所示，测试框架的形成是在撰写大量测试脚本的过程中不断抽象封装出来的，然后，再用这个不断完善的框架，改写原有的测试脚本。循环往复这个过程，你就会慢慢获得一个独一无二的、又完全适合你工作的接口测试框架。

其实到这里，我们上面说的只能算是一个调试代码，还不能算是一个测试框架。上面这些代码所有的返回值都打印到控制台后，为了完成接口测试，你需要时时刻刻看着控制台，这还不能算是自动化，只能说是一个辅助小工具。

在这里，你应该让全部测试结果都存储到测试报告里面，同时通过一个测试驱动框架来完成各个模块的驱动，这也是为什么你在学习任何一种框架的时候，总会遇见类似 Java 的 JUnit、Python 的 Unittest 的原因，因此，上面的 Common 类还需要和 Python 的 unittest 一起使用，才算是一个完美的测试框架。

至于你自己的 Common 类怎么和测试驱动框架相结合，这部分内容就留给你在未来的接口测试工作中，自己去学习并完成了。

总结

今天，我们一起学习了一个测试框架的诞生过程。测试框架就是在你测试脚本中不断抽象和封装得来的。今天我们课程的内容充斥着各种代码，如果你的代码基础稍微比较薄弱，并没有完全记住上面的内容，那么我希望你记住从测试脚本到测试框架的转化过程：

不断撰写测试脚本，所有的抽象和封装都是站在已有的测试脚本基础之上的；

多观察已经写好的测试脚本，找出其中的重叠部分，最后完成封装；

以上两步是一个不断循环又循序渐进的过程，你要在你的工作中始终保持思考和警惕，发现重复马上进行框架封装。

最后我想和你强调的是，测试框架的封装和抽象过程并不是一蹴而就的，它是靠一点一点的积累得来的，因此，你要通过自己的实践，慢慢积累和完善你的测试框架，而不要妄想一次就能有一个完善的测试框架。我相信，当你通过写脚本完成整个项目的接口测试后，你一定会得到一个完美的测试框架。

思考题

在我讲的最后一个多接口测试脚本，其实也并不是最完美的修改，你能提出更好的修改意见吗？如果它可以抽取到你的框架中，那么是完成一个什么样任务的类或者函数呢？

我是陈磊，欢迎你在留言区留言分享你的观点，如果这篇文章让你有新的启发，也欢迎你把文章分享给你的朋友，我们一起探讨和学习。

05 | 案例：测试框架如何才能支持RESTful风格的接口？

你好，我是陈磊。

在前面的课程中，我们一起学习了如何把流程化的测试脚本，一步一步抽象成你自己的测试框架。无论你用的是什么编程语言，封装和抽象自己的测试框架都会让你的接口测试任务事半功倍。

我相信你在平时生活或工作中，一定会接触到各式各样的软件系统，而现在的软件系统和 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（战场）”这个系统，那么你一定在它的 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:
    # 如果有参数，那么通过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

在上面的代码中，你可以看到，我们为了实现 HTTP 协议的 Put 和 Delete 方法，自己封装了 put() 函数和 delete() 函数。其实，要实现 RESTful 风格的接口测试，你只要封装 HTTP 协议对应的 Method 方法就可以了，这样，你的框架就能完美的支持 RESTful 风格的接口了。完成了这个操作后，我们的 Common 类就既可以完成 HTTP 协议接口的测试，也可以完成 RESTful 接口的测试了。

总结

到这里，我们已经结束了今天的课程了。我们今天主要完成了 RESTful 风格接口的测试，对比之前的例子以及你自己的测试框架，针对框架中 RESTful 里缺失的部分，我为你提供了对应的解决方法。

在文中我讲了很多内容，但是完成 RESTful 风格接口测试，主要是通过两步操作，来为你的测试框架添加对应接口的测试能力的：

借助外力。目前网上已经有很多成熟的、各式各样的支持库，你要尽量拿来为己所用，而不要从零建设，这样，既弥补了我们开发能力不强的短板，也能提高我们的研发效率。

自己封装。你要注意的是，自己封装和借助外力并不互相冲突，你要借助外力，然后将它封装到你自己的框架中，这是一个借力打力的好方法。

随着我们的课程的不断深入以及内容的不断丰富，我相信，你最终会获得一个完全适合你自己，又可以解决实际工作任务的测试框架，这也是你自己的接口测试武器仓库，里面有解决各种接口测试问题的方法。它会是一个私有仓库，里面每一个武器都是为你自己量身定制的，因此，每一件武器你用起来都会更得心应手。

思考题

我今天讲了 RESTful 接口测试，并为你的私有测试框架添加了各式各样的新武器，那么，你能用你现在的新武器，解决一个你负责的 RESTful 的接口测试吗？在今天的框架中，随着你实际工作的使用，你又有了什么样的新设计呢？

我是陈磊，欢迎你在留言区留言分享你的观点，如果这篇文章让你有新的启发，也欢迎你把文章分享给你的朋友，我们一起探讨和学习。

06 | 接口测试平台：工具和框架不可以兼容？

你好，我是陈磊。很高兴在接口测试课程中再次遇见你。

到目前为止，我们的课程重点介绍了完成测试任务的两种接口测试手段，第一种是使用如 Postman 这样的工具，第二种是打造属于你自己的测试框架。上节课我们还一起学习了 RESTful 风格的接口，并针对它的特点完善了我们自己的测试框架。

这节课我就教你如何用工具和框架的组合搭建接口测试平台，让你能更快速地完成测试任务。

工具的便捷性与框架的灵活性可以兼得

说到这儿，你一定有一个困惑，在前面我先讲了 Postman 这款非常好用的 HTTP 测试工具，后来又讲了怎么自己动手封装接口测试框架，它们各有特点，比如工具有便捷性，框架有灵活性，这无疑是两条都可以通向罗马的路，是两种都可以完成接口测试工作的方法，那学会一个不就可以了，为什么两个都要学会呢？

而且工具和框架，这两件事看起来互不相干，甚至有些互相排斥，那么这两种接口测试技术手段能相互支持，能融合到一起吗？下面我就来回答这个问题。

其实，工具和框架，这两条通向罗马的路可以并成一条快速通道，让你大踏步进军罗马。所以我既建议你要掌握一款好用的工具比如 Postman，也建议你用自己的技术沉淀出自己的框架，如果你能正确地混合使用它们，实质上就可以搭建起一个接口测试平台，帮你更快速地完成测试任务。

在脚本的设计过程中，这样做有两个好处。

一是能充分发挥 Postman 界面化的优势，快速完成大量的脚本撰写工作；二是通过你自己的框架完成测试脚本的执行，所有的过程代码都会存储到你自己的代码仓，这样，既可以留下测试的过程资产，也便于版本控制，这也为持续集成、持续交付等平台提供了无人值守的、按需驱动测试的途径。

此外，这样做也可以提升团队的工作效率。

在我的团队中，有一些小伙伴很不喜欢写代码，相反，他们更喜欢使用工具，工具用得也非常溜，我相信在你的团队中也会存在这样的情况。但是，仅仅依靠工具，只能一个人完成一件事情，这并不方便团队内部的团队合作、交接和技术积累。

但是，我们又没有办法让所有人一下子都喜欢上写代码，那么该如何降低代码编写门槛呢？通过工具和框架搭建接口测试平台，其实就是一个很好的解决方案。这样，你既可以让你的团队有技术积累，又能给团队中一些编码能力比较薄弱的小伙伴学习时间，最重要的一点是，这不会影响整个工作的进度。

工具的便捷性可得

不知道在学完前面的课程后，你是不是还用过自己的 Postman，当你再次打开 Postman 的时候会发现，你之前用它来完成的测试脚本是被保存下来了的，就如下图所示：

可以看到，我上次使用 Postman 测试“战场”系统的脚本还是存在的，如果你忘记了，可以在课后看一看你用来做测试的 Postman，再分别看看那四个请求。

到这里，我想请你先闭上眼睛回想一下，你之前使用 Postman 做接口测试的整个流程，是不是清晰可见？你也可以同时回忆一下，你用自己封装的 Common 类，编写“战场”系统测试脚本的过程。你会发现，和工具的使用过程相比，在这里你不太容易回想出自己每一步都做了什么。

这也是 UI 操作和代码操作的区别之一，UI 操作更加直观，可以在你的脑海里留下更深刻的印象；而代码操作给人留下的印象就比较模糊，但是，通过用代码写脚本来完成接口测试，比较便于维护、团队合作以及留存。

讲到这你肯定会问：“你把用 Postman 类工具完成接口测试，以及自我封装测试框架这两种方法各打五十大板，那它们到底哪个好？”其实我的目的并不是想让你分出个好坏，好坏之分都是相对的，每个人的习惯和喜好都不相同，但是我们却可以把它们的优点都利用好，把这两种技术的优势都发挥出来。

我们利用 Postman 设计接口测试直观、快速的优势，将它变成接口测试脚本的初始脚本的编写工具，其实 Postman 也可以配置 Chrome 插件录制请求，这些在 Postman 官方已经有很详细的介绍，所以我就不在这里详细讲解了，如果你感兴趣，课后可以自行学习。

我们以之前的测试脚本为例，选择第一个单接口接口测试的脚本，在右侧点击 Code 按钮。

在弹出框中，你可以选择各式各样技术栈的测试脚本，在这里，我们还是用在之前例子中所选取的 Python，我们框架的依赖库是 Requests，这样你就可以看到显示出的代码了，就如下图所示。看到这些代码，你是不是已经开始觉得，通过这样的处理来编写脚本更加容易。

由此可见，和写代码相比，使用 Postman 来设计接口测试要更容易使用，对于代码基础比较薄弱的测试工程师来说，这种方法也更容易掌握。

框架的灵活性亦可得

在刚刚高兴的心情慢慢冷静下来以后，你是不是在心里默默地埋怨我？既然有这么简单的方法，为什么还一直让你学习一门编码技术，还建议你如果什么都不会，可以学习一下 Python？这是因为工具生成的代码可读性特别差，也并不适合我们将它作为团队的技术积累留存。

现在，我们一起看一看由工具生成的代码。先来看看第一个接口首页单接口对应的代码：

import requests

url = "http://127.0.0.1:12356"

headers = {
    'cache-control': "no-cache",
    'Postman-Token': "8c6247bb-744a-43d3-b27d-9e51af923c5d"
    }

response = requests.request("GET", url, headers=headers)

print(response.text）

上面的这个代码你是不是似曾相识？这就和我们第一次写的第一个接口的单接口测试代码一样，是一个流水账一样的脚本，这些代码如果原模原样地存到你的代码仓中，对你再次使用它没什么好处。那么在这基础上，我们可以将它修改成自己框架的脚本，就如下面这段代码所示：

   # 引入你的框架
   from common  import Common
   #访问uri
    uri_index = "/"
    #调用你的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 = "http://127.0.0.1:12356/login"

payload = "username=criss&password=criss"

headers = {

'cache-control': "no-cache",

'Postman-Token': "fdc805e1-4406-4191-ae44-ab002e475e03"

}

response = requests.request("POST", url, data=payload, headers=headers)

print(response.text)

如果你还记得我在测试代码及框架那一节课（也就是04）中讲的内容，就会发现，它和那里最开始部分的代码实现几乎一致，这和我们自己手动写的代码最大区别就是，它少了很多注释，而多出一些访问头信息，也就是上面代码的 headers。

headers 在我们“战场”系统的测试中并不是必须传递的参数，但是 Postman 这种工具会将其添加默认值传递给服务器。这是由这个工具添加的，你在写脚本时，如果它是非必填的，你可以忽略它。但是，工具这么做是为了匹配所有的情况，所以它会做一些和我们这次测试不相干的工作。

难道说 Postman 这么好的功能，对我们来说就没有一点好处吗？其实我们在上面代码的基础上，将其修改成引入我们自己框架的测试代码，完成修改后，再推送到接口测试项目的代码仓中，就如下面这个代码所示：

from common import Common
uri = "/login"
payload = "username=criss&password=criss"
comm = Common('http://127.0.0.1:12356')
response_login = comm.post(uri_login,params=payload)
print('Response内容：' + response_login.text)

这也无疑加快了我们测试脚本的编写，在上面这个过程中，我们也很容易再次发现需要封装到框架中的公共方法，这样循环下来，就加快了我们测试脚本的积累速度，同时我们也就可以有更多时间用在框架的维护上了。

通过代码的改写和封装，我们就可以将工具生成的代码完美结合到我们的框架中了，当我们需要修改已经存储在代码仓库的脚本时，我们只需 pull 代码仓的代码，就可以看到易读、易维护的测试脚本了。

总结

我们今天的课程到这里就结束了，现在你闭上眼睛回顾一下，如果你的头脑中就只有用 Postman 快速编写脚本，自己框架留存执行的话，只能说明你今天学习得很认真，但是并没接受我想告诉你的主旨思想。

我今天以 Postman 工具和你自己的框架相结合的例子，告诉你如何建立一个你自己的测试平台，你可以通过三步完成工具加框架的组合方式：

借助 Postman 这类工具的易学、易操作的特点，将它变成你测试脚本中快速创建的脚本撰写工具；

利用工具提供的导出代码功能，将其导出成我们流程化的测试代码；

通过我们自己的框架，改写我们通过工具导出的脚本。

最后，你的测试脚本可以存入代码仓中为持续集成平台提供持续验证，这就完成了一套简单又灵活的接口测试平台的建设。

实际上，在本节课中，我更希望帮你建立一种解决问题思路，测试工程师的技术普遍会稍微弱于开发工程师，你要善于利用各种技术手段来帮助自己解决问题。

无论你团队中的小伙伴是用 Postman 生成测试脚本，再通过修改集成自己的框架，还是直接通过框架写测试脚本，它们都殊途同归，都是以最终统一的方式推送到了代码仓库中。这样，就不会让代码能力变成阻塞最终工作的一个关键节点，同时，这对于使用 Postman 编写脚本的小伙伴来说，他们也会越来越熟悉自己的框架，逐渐提升自己的技术能力，并加强自己的代码能力。

思考题

今天的课程中，我们把“战场”系统的测试脚本又拿出来测试了一次，不知道你是不是有点厌烦了，今天我仅仅给你演示了第一个和第二个单接口测试脚本，它们从工具到框架的演变过程，那么，你可以将后面的两个单接口测试脚本自己完成，并在留言区回复给我吗？如果你已经开始将这个方法应用到你的工作中了，那么我也希望你能将自己在使用过程中的心得体会分享给我。

我是陈磊，欢迎你在留言区留言分享你的观点，如果这篇文章让你有新的启发，也欢迎你把文章分享给你的朋友，我们一起来探讨和学习。

07 | WebSocket接口：如何测试一个完全陌生的协议接口？

你好，我是陈磊。很高兴你又来和我一起探寻接口测试的奥秘了。

我们在前面一起学习了怎么分析和完成一个 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 测试的想法也就胎死腹中了。

但是，我可以借助 Fiddler 分析 WebSocket 的接口，这也和我们一开始给 Fiddler 这款工具的定位一样，那就是通过它辅助分析我们的被测接口。

自己写 WebSocket 测试代码

当用已有工具基础解决 WebSocket 接口测试这个想法破灭了后，我开始寻求通过编写代码，解决 WebSocket 的接口测试。在这里，我还是建议你要以你自己的技术栈为出发点，寻找解决问题的方法。由于我的主要编程语言是 Python，因此下面一些讲解的示例代码段，我还是以 Python 为例，但是你要知道，解决问题的思路并不限于 Python 的编程语言，它可以是你使用的任何其它语言。

我发现 Python 提供了 WebSocket 的协议库，因此我只要用它完成客户端的撰写，就可以进行接口测试了。这里，我写下了第一个 WebSocket 的调用代码（这里我们以 http://www.websocket.org/demos/echo/ 为例)，如下面图中所示，我在代码里面写了详细的注释，你肯定能看懂每一句话的意思。

#引入websocket的create_connection类
from websocket import create_connection
# 建立和WebSocket接口的链接
ws = create_connection("ws://echo.websocket.org")
# 打印日子
print("发送 'Hello, World'...")
# 发送Hello，World
ws.send("Hello, World")
# 将WebSocket的返回值存储result变量
result = ws.recv()
# 打印返回的result
print("返回"+result)
# 关闭WebSocket链接
ws.close()

不知道你发现没有，上面的代码和 HTTP 协议的接口类似，都是先和一个请求建立连接，然后发送信息。它们的区别是，WebSocket 是一个长连接，因此需要人为的建立连接，然后再关闭链接，而 HTTP 却并不需要进行这一操作。

我相信你肯定还记得在测试框架那一节（04）中，我们学习了一些线性的接口测试代码，然后通过分析这些代码抽象出 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，ws，http就是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":
       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），你该如何从零开始完成接口测试任务。

针对一个陌生协议的第一次接口测试，你要保持自己敏锐的测试嗅觉，依据自己的技术基础，尽快解决问题。总地来说，你可以通过三步快速完成测试任务：

借力开发工程师。你首先该借力就是开发工程师，但你不要进入开发工程师给你的那种，从技术基础和理论开始学起，再逐步应用的学习脉络。你要一击致命，直接把他的客户端代码拿来，尽最大可能挪为己用，将其变成自己的接口测试代码。

站在自己的技术栈之上，完成技术积累。如果开发工程师的代码并不能拿来使用，那么你就需要站在自己的技术栈上寻求解决方法，这其中既包含了你已经熟悉的测试工具、测试平台，也包含了自己的测试框架和编码基础。

归入框架。无论你使用哪一种方法，在完成测试工作后，你还是要掌握对应的理论基础，同时想办法将这个一开始陌生的接口，通过自己熟悉的方式合并到你自己的框架中，不断扩充自己框架的测试能力，不断丰富你自己的测试手段。

思考题

我们今天一起学习了如何破解陌生协议接口测试难题的步骤，那么面对 WebSocket 的接口测试任务，结合你现有的技术栈，你是不是也有你自己的解决方案呢？你工作中如果有类似的陌生协议（既可以是第一次接触的协议，也可以是企业私有协议），你是如何解决的呢？欢迎你在留言区中留下你的疑问和你的做法。

我是陈磊，欢迎你在留言区留言分享你的观点，如果这篇文章让你有新的启发，也欢迎你把文章分享给你的朋友，我们一起沟通探讨。

08 | 测试数据：是不是可以把所有的参数都保存到Excel中？

你好，我是陈磊。

课程到现在，我们已经一起从接口测试思维的训练，走到了接口测试技术的训练，随着学习的不断深入，你应该也有了一个自己的测试框架，虽然这个框架可能还很简陋。但是任何事情不管多晚开始，都好于从未开始，因此学到现在，你已经迈出了接口测试以及其测试技术的第一步。

做任何事情，从零到一都需要莫大的勇气和坚定的决心，在这个过程中，你要将自己挪出舒适区，进入一个陌生的领域，这确实很难。但如果你和我一起走到了这一节课，那么我要恭喜你，你已经完成了接口测试从零到一的转变，后续从一到无穷大，你只需要随着时间去积累经验就可以了。

如果把接口测试比喻成要炒一盘菜的话，那么我在之前的全部课程中，重点都是在讲解如何完成接口测试，也就是教你如何炒菜。我也教过你如何解决接口测试的需求，为你提供了解决问题的能力和手段，这也就是在帮你建造一个设备齐全的厨房，帮你一起完成接口测试任务 。

有了精致的厨房后，我也告诉了你要怎么制作顶级的厨具，也就是接口测试的技术方法和实践方式。这些厨具既有锅碗瓢盆，也有刀勺铲叉，这里的锅碗瓢盆就是你的测试框架，刀勺铲叉就是你使用框架完成的测试脚本，这其中既包含了单接口的测试脚本，也包含了业务逻辑多接口测试脚本。

那么如果想炒菜你还需要准备什么呢？毫无疑问那就是菜，所谓“巧妇难为无米之炊”，即使你有高超的手艺，有世界顶级的厨具，但如果没有做菜的原材料，那也没办法把菜做出来，就算是世界顶级大厨，也无法完成这样的任务。

今天我就顺着这个思路，和你讲讲菜的准备，也就是接口测试的数据准备工作。

测试数据的好处：打造自动化测试框架

随着你不断封装自己的测试框架，你的框架就始终处于等米下锅这样一种的状态，而米就是测试数据。我在之前的课程中，都是将测试数据直接写在代码里，赋值给一个变量，然后通过接口测试逻辑来完成测试。

说到这，我还是把我们之前用过的”战场“这个系统拿出来，看一看它“选择武器”这个接口测试脚本（你可以回到04中查看），虽然你现在对怎么撰写、怎么封装类似的接口脚本都已经烂熟于心，但我们还是先看一下它的代码段：

# 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 变量赋值的，在做测试的时候，你还需要不断修改这个参数的赋值，才能完成接口的入参测试，这不是一种自动化的测试思路。

因此，你需要将数据封装，通过一种更好的方式，将数据存储到一种数据存储文件中，这样代码就可以自行查找对应的参数，然后调取测试框架执行测试流程，接着再通过自动比对返回预期，检验测试结果是否正确。

这样做有两个好处。

无人值守，节省时间和精力。我们将所有的参数都存储到外部存储文件中，测试框架就可以自行选择第一个参数进行测试，在完成第一个测试之后，它也就可以自行选择下一个参数，整个执行过程是不需要人参与的。否则的话，我们每复制一组参数，就要执行一次脚本，然后再人工替换一次参数，再执行一次脚本，这个过程耗时费力，而且又是一个纯人工控制的没什么技术含量的活动。

自动检测返回值，提高测试效率。如果你用上面的代码段完成接口测试，就要每执行一次，人工去观察一次，看接口的返回是不是和预期一致，人工来做这些事情，不只非常耗费时间，效率也很低下。但是通过代码完成一些关键匹配却很容易，这可以大大提高测试效率，快速完成交付。

怎么样，看到这些好处，你是不是也想马上给你的框架加上数据处理的部分了呢？

如何选取测试数据

现在我们就马上开始动手，为你的框架加上参数类。

首先，你先要定义一种参数的存储格式。那么我想问你的是，要是让你选择把数据储存在一个文件中，你会选择什么格式的文件呢？

我相信你肯定和我的选择一样，用 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 < nCountRows:
    ParamOneLinelist=self.getOneline(iRowStep)
    ParamOnelineDict = {}
    while iColStep<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<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 里面了，具体内容如下图所示：

通过上面的参数类你可以看出，在这个 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<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、Java 的Junit等，你就可以通过断言完成预期结果的自动验证了。

总结

今天我们接口测试数据准备的内容就到这里了，在接口测试的工作中，作为“巧妇”的测试工程师，还是需要参数这个“米”来下锅的，虽然我们之前课程中的代码涉及到参数的处理，但是都很简单粗暴，一点也不适合自动化的处理方式，因此今天，我带你完成了参数类的封装。

有的时候，我们也把参数类叫做参数池，这也就是说参数是存放在一个池子中，那我们准备好的池子就是 Excel。我相信未来你也会不断扩展自己参数池的种类，这有可能是由于测试接口的特殊需求，也有可能是由于团队技术栈的要求。因此，我们封装参数池是通过简单工厂设计模式来实现的，如果你的代码基础并不好，那么你可以不用搞清楚简单工厂设计模式是什么，只需要知道如何模拟上述代码，再进行扩展就可以了。

一个好用的测试框架既要有很好的可用性，也要有很好的扩展性设计，这样我们的私有接口测试武器仓库就会变成可以不断扩展的、保持统一使用方法的武器仓库，这样才能让你或者你的团队在面对各种各样的测试任务时，既可以快速适应不同接口测试的需求，又不需要增加学习的成本。

思考题

今天我们一起学习了参数类的设计，并且将它应用到”战场“系统的接口测试脚本中，后续我又告诉你为了能够完成代码的自动验证，你需要引入一些测试驱动框架，那么，你的技术栈是什么？你在你的框架中选取的测试驱动框架又是什么呢？你能将之前”战场“系统的全流程测试脚本通过参数类完成改造吗？我期待看到你的测试脚本。

我是陈磊，欢迎你在留言区留言分享你的观点，如果这篇文章让你有新的启发，也欢迎你把文章分享给你的朋友，我们一起沟通探讨。

09 | 微服务接口：怎么用Mock解决混乱的调用关系？

你好，我是陈磊。

欢迎你继续和我一起学习接口测试，到目前为止，我们已经学习了接口测试的逻辑模拟，也就是测试辅助工具和测试脚本代码，也学习了如何选取和通过代码调用测试参数，掌握了这些内容，你就算是一个接口测试的老手了。无论你的被测接口是一个你熟悉的协议，还是一个陌生的协议，它们都不会耽误你的工作进度了。

这节课是我们专栏的最后一节课，我想给你讲一讲关于微服务的接口测试。

现如今在我的工作中，我主要面对的就是微服务测试，每个服务都是 RESTful 接口。在最开始的微服务改造过程中，我的测试其实比之前的业务测试更容易，每一个接口通过测试框架来编写测试脚本就可以完成执行了，而且一次写完后再通过平台调用，也显得很轻松。但是这种美好的场景并没有持续多久。为什么呢？ 你先听听我的故事。

微服务下混乱的调用关系

开发团队开始采用微服务架构开发系统的时候，我的测试团队也开始同步学习对应的测试技术，我也像从前一样，逐步封装自己的测试框架，并且采用 Postman 和 Python 代码，完成接口测试脚本的快速积累，同时引入了参数类，完成了 Excel 参数的封装调用。

在开始的一些项目中，只要开发工程师提交了代码仓库主干的合并请求后，除去代码的静态扫描外，持续集成平台会自动调取一个开源的智能化单元测试框架，来完成单元测试，通过后它会自动部署被测系统，然后再执行测试脚本，这整个流程全部是流水线自动驱动完成的。

一般来说，开发工程师在开发前期就已经定义好了微服务接口，测试工程师和开发工程师几乎是同步开始进行各自的开发任务。但是，这种和谐的工作场景很快就被蜘蛛网一样的微服务调用关系给破坏了，几乎所有的项目都会出现相互依赖的关系，比如说服务 A 依赖服务 B，服务 B 依赖服务 C，如下图所示：

这种混乱主要体现在：

当持续集成流水线部署服务 A 的时候，由于对应的开发工程师团队也在做同步改造，导致测试环境的服务 B 不可用；

由于服务 B 依赖服务 C，而服务 C 还没有开发完成，导致即使服务 A 和服务 B 都没问题，但也没有办法完成服务 A 的接口测试。

其实这种服务 A 依赖服务 B，服务 B 依赖服务 C 的依赖方式还算简单，还有更多微服务随着开发越来越复杂，服务之间的调用关系就像蜘蛛网一样错乱，让你摸不清外部依赖到底有几层，以及一个接口到底依赖了几个外部接口。

这就导致了虽然被测系统已经开发完成，测试脚本也准备就绪，但是测试工作就是没办法进行的悲惨结局。面对这种局面，我当时心里确实很不舒服，因为自己做了那么多努力，到头来却被一个不是由自己负责的服务卡住了工作进度，这感觉就像是用尽了全身的力气，却一拳打到了棉花上，自己有再多的劲儿也没处使。

Mock 框架的抉择：用什么实现服务 B 的替身

那作为测试工程师，面对这样的情形，我们该怎么办呢？

我当时想到的就是使用 Mock 服务。其实 Mock 服务是一个错误的说法，关于这一点我推荐你看一下 Martin Flower 的这篇叫做TestDouble的文章，一般我们将 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 框架和 Python 语言的mock 框架。

如果你的团队技术基础相对比较薄弱，那么我推荐你看看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 工具，那么你在工作中，有没有遇见过被测系统因为外部依赖，而不得不阻塞项目进度的时候呢？你又是怎么解决的呢？

我是陈磊，欢迎你在留言区留言分享你的观点，如果这篇文章让你有新的启发，也欢迎你把文章分享给你的朋友，我们一起沟通探讨。

结束语 ｜ 如何成为一名优秀的测试工程师？

你好，我是陈磊。

不知不觉中，我们已经相伴一起学完了接口测试的全部课程，我相信，你现在已经能独立完成绝大部分接口测试的工作了。在这最后一节课呢，我不想再和你说接口测试乃至测试框架还应该做什么，而是想来和你聊聊如何成为一名优秀的软件测试工程师。

在前面的课程中，我一直在和你说测试工程师是个技术岗位，不是“点工”。在把我们的课程全部都学完后，你现在是不是深有同感？因为就接口测试这件小事我们都可以学习这么久，从接口是什么、如何开始接口测试开始，到后来的用工具和代码解决测试问题，直到最后的参数数据层的封装、外部解耦服务的使用等等。

在我们课程的留言中，有这样一个问题：“接口测试能力的高低体现在哪里？”其实，评价一个接口测试人员的能力水平，主要就是看他解决问题的能力如何， 这也包括了他技术栈的熟练程度，测试用例方法以及业务知识的掌握程度。在专栏的全部课程中，我们一起经历了接口测试从简单到深入的过程，而这个过程呢，其实也说明了测试工程师在各个阶段应该达到的能力。这样，测试工程师就可以划分为初级测试工程师、中级测试工程师和高级测试工程师。

初级测试工程师。他应该懂得接口测试，可以使用接口测试工具完成接口测试任务；他也要有接口测试的思维，能够将这种思维在实际项目中应用落地。

你也许会质疑这个要求：对于一个初级测试工程师，这会不会太难了？但就像我之前说过的，如果你现在去查一些涉及测试工程师的招聘需求，你就可以看到这项要求，现实就是如此，这部分内容也是我在“初级技能篇”中教给你的东西。

中级测试工程师。他要能编写测试代码，可以使用一种编程语言完成接口测试任务。

这短短两句话的要求其实需要很多努力。首先是编码的能力，这在很多测试面前是一个难以逾越的鸿沟，但是如果你跨过这个鸿沟，你就会发现后面会一马平川。

其次，能用编程语言模拟测试协议并完成测试，这确实需要你多了解一下对应编程语言的支持库。

最后，也是最难的要求了，你要能把接口测试思维和技术栈结合一起，并遵从以后的内部编码规范，约束开发的脚本，这其实也是我在“综合技能篇”中给你讲的内容。

高级测试工程师。他必须有能力封装适合团队的测试框架，并能提供给持续集成、持续交付平台调用。

要保持自己高级测试工程师的段位，你需要不断努力，不断学习和总结，时刻保持作为一个测试框架维护者的心。在这里，测试框架封装主要就是我在“进阶技能篇”中教给你的框架的抽象，参数化的设计，不同接口、不同协议的支持等内容。

那么如何使自己不断成长为高级测试工程师呢？

在留言区，我也看到了很多人对测试工作感到迷茫和困惑，有些人困惑于长期的手动功能测试，自己技术上没有提升，有想学习技术的想法却又不知道如何开始学习；还有些人学了一些技术，却不知道如何把这些技术应用到实际项目中，产生了学习和工作之间的分层，不能学以致用。

其实，在工作中有困惑不是一件坏事，这反而说明你是一个在不断成长的人，因为只有发现问题才能去解决问题。

就接口测试来说，学习路线就如同我在专栏中讲解的一样，从整理输入到使用工具，再到封装框架来完成测试任务，这其实也是一个技术的实践路线。如果你不知道如何将这些内容和工作结合到一起，不妨试试我在一开始说过的 Postman 这类小工具，用它敲开你的接口测试任务大门，随着工作的不断深入，后续你在写测试脚本和封装框架也就慢慢水到渠成了。所谓的高级测试工程师，也就是这样一步步修炼出来的。

与此同时，我也希望你能通过我们这个专栏的学习思路，学会怎么去掌握任何一项陌生的测试技术，因为这些测试工作的内容可能有所不同，但学习的方法和思路却可以是共通的。

我建议你可以通过三步来规划你的学习：

从实际动手开始学习测试技术。你最好还是从一个实际例子出发开始学习，这就和大部分编程语言都从 Hello World 开始一样。直击问题，这样才更能激发你的学习兴趣。

不断遇见问题，不断解决问题。你在实际工作中，肯定会遇见很多问题，你一定要一个一个去解决，并不断地从问题中总结可复用的经验，这样就会形成一套你自己独特的学习思路，以及更适合你自己的技术学习方法，更重要的是，这样学习更不容易遗忘。

掌握理论知识。我之所以将理论知识的学习放到最后，是因为当你知道如何用一个测试技术解决问题的时候，再去学习理论，就不会出现由于理论枯燥、记不住而放弃学习这样的情况了。同时，实践后再学习理论，每看到一个知识点你就会有“原来是这样实现的啊”这种感觉，你就很容易理解并记住它了。

所以说到这，你完全可以把接口测试看作是你技术学习的起点，而不是终点。

我也希望我的课程设计对于你未来的技术学习有一些帮助，在工作中，你肯定会遇见各种各样的新问题，你完全不必担心，因为任何一门技术都是由于工作中出现了痛点，接着才会推动整个技术向前发展，你只要时刻关注自己在工作中碰到的具体问题，还有阻塞工作的问题点，然后学习用技术手段解决问题就可以了。

那么如何解决新问题呢？我建议你学会从自己熟悉的内容出发，在自己的技术或者实践经验基础上去解决。就如同在接口测试中，即使你没有理想的接口测试文档，你也能借助自己熟悉的工具完成接口分析，然后逐渐封装出自己的测试框架。并由此慢慢掌握一个你不是很熟悉的解决问题的方法，最后通过不断地实践积累，不断增加你自己测试框架的测试能力，让它也和你一样，不断解决测试中碰到的新问题。

我们一起学习了这么长的时间，但终归是要为我们的课程画上一个句号，现在我希望你思考一下这几个问题：

你最初在留言区立下的目标实现了吗？

你的测试框架的名字是不是已经有了一个实际的测试框架来承载呢？

你掌握接口测试的思维了吗？

你掌握了如何学习一项测试技术的方法，并将它应用到实际工作中了吗？

上面这些问题你只要在心里默默有了答案就好，如果绝大部分问题你都有了肯定答案，那么我要恭喜你并欢迎你走入测试技术的大门，你的未来十分可期。

如果你对接口测试或者其他测试技术还有心得或者问题，那么你要记得，我会一直在留言区等你，这里还有很多和我们一起学习和分享的人，你可以随时回来，和他们分享你的问题和困惑，当然也包括你的成功。

如果这个专栏曾经帮到了你，或正在帮助着你，也欢迎你把它分享给你的朋友和同事，我们一起沟通、探讨。我是陈磊，在前行的路上，记住，你永远也不孤单。

最后，我为你准备了一份结课问卷，题目不多，2～3 分钟即可完成。希望你能畅所欲言，把自己真实的学习感受和对我们课程的意见表达出来，我一定会认真看。

点击下面的图片即可直接填写问卷，成功提交的同学，将有机会得到由极客时间商城独家销售的“数据结构与算法知识地图”或者价值 99 元的“专栏兑换阅码”，我期待你的反馈。江湖路远，我们总会再见。