摘要:本文主要关注的是接口测试。所谓接口测试,就是检查系统提供的接口是否符合事先撰写的接口文档。作为接口测试的解决方案,我们必须具备通用性与易用性。
开始
最近几年,前端测试渐渐被人重视,相关的框架和方法已经比较成熟。
断言库有should, expect, chai。 单元测试框架有mocha, jasmine, Qunit。 模拟浏览器测试环境有Phantomjs, Slimerjs。 集成测试任务管理工具有karma。此外,还有一堆诸如Selenium、nightwatch(冰火出戏)等各色思路不同的关注UI测试的工具。
本文主要关注的是接口测试。
接口是前后端协作的桥梁,是系统得以顺利运行的关键。所谓接口测试,就是检查系统提供的接口是否符合事先撰写的接口文档。数据结构是否完备,数据类型和取值是否符合标准。
实际上,接口测试由后端来做会比较方便,但是由于一些实际原因(后端大哥比较忙,人手不足等),后端往往不能确保接口数据一定符合接口文档规定。此时,作为前端,我们为什么不能另辟蹊径,探索一番呢?
接口测试最大的难点是什么?我觉得是登录状态的获取。
众所周知,一个系统的大部分接口都只会对登录用户开放。在测试接口之前,首先必须确保这次会话已经登录,发送给后台的请求中必须携带登录后获取的 cookie。面对这个问题,最直接的想法是在后台模拟登录。
mocha 可以运行在node中,理论上我们完全可以在node环境下,使用一些http client( request, superagent等),模拟进行登录。手动获取cookie,并加在之后的每一次接口请求中。获得到接口数据后,就可以结合 mocha,愉快地进行测试了。
可是,实际实验之后,我发现这个方案存在一些缺陷。
难以封装复用
不同系统的登录流程并不相同。相当多的系统要求在登录时需要附带其他的 cookie 或者额外的表单参数。
以本人测试的系统为例,登录时就需要带上一个 key 为 jsessionid 的 cookie,以及一个 key 为 nlt 的表单参数。这个 cookie 是怎么来的呢?是访问登录页时返回的 set-cookie 头设置的。这个表单参数又是怎么来的呢?是藏在登录页面的表单中的一个隐藏域。
所以,如果简单地用 http client 向登录地址发一个 post 请求,仅仅带上自己的用户名密码,你就会发现,登录毫无疑问的···失败了。
更烦人的是,如果你的系统登录依赖 SSO,内部存在 302 请求转发,那么很有可能会出现 cookie 丢失的情况(有两个 set-cookie header,浏览器能识别并正确设置,但是很多 http client 只能拿到最后一个)。
我们当然可以动用各种奇技淫巧,手动获取和设置各种特定的 cookie,去网页里爬出隐藏域的值。可是,这一切,仅仅对特定的系统有效。如果换一套系统,你就必须重新研究一遍登录流程,重新写一遍模拟登陆代码,感觉还是挺崩溃的。
难以应对验证码
上面的问题仅仅只是使用不便,这个问题就是直接就给该方案判了死刑。当然,爬虫界也有不少应对验证码的解决方案,比如依托 OCR 软件啊,人工判别 API 啦, 更厉害的还有自己做图像处理和算法进行识别。可是,作为一个简单的接口测试,这种方案是不是太小题大做了点?
mocha 也可以在浏览器环境运行,当后台模拟登陆遇到困难,我们很容易想到,在浏览器环境进行测试是否可行呢?
普通的浏览器环境确实会有一定问题,那就是前端的老大难:跨域。
我们的目的是前端接口测试,原则上不应也不能让后台搭配测试工作去改接口,所以,CORS,jsonp 都是不可行的。
难道这条路又堵死了?非也,我们还有一个选项,那就是——浏览器扩展。
以 chrome extension 为例,只要在 manifest.json 文件中配置好 permissions ,扩展发起的请求就可以成功跨域。事实证明,完全没有问题。不仅可以跨域,还可以携带登录后的 cookie,所以,只要在浏览器正常登录后,再打开插件相关页面进行验证,就可以解决登录状态的问题。简直完美。
既然定下了方案,下一步就是设计。作为接口测试的解(wa)决(keng)方(zhi)案(lv),我们必须具备通用性与易用性。使用者只需提供配置,不需要再进入源代码修改打包。
本方案中中测试框架和断言使用 mocha + chai。这方面的资料汗牛充栋,本篇不再赘言。
apiTest │ package.json │ webpack.conf.js │ api.conf.dist.js │ api.conf.dist.js.map │ index.js │ manifest.json │ test.png ├─config │ index.js ├─css ├─html ├─js ├─lib └─TestCreator
其中,index.js 是入口文件; manifest.json 是 chrome 插件配置文件; config/index.js 是用户测试配置文件。css, html, js, lib 都用于存放资源文件。
使用流程安装插件(如果已安装过就可以省略)
提供测试配置
用户去系统登录页完成登录
点击插件图标,打开新 tab 页
在 tab 页中进行测试,并在页面上显示结果
让我们从 manifest.json 文件开始,一步一步深入,看目标流程是如何实现的。
- manifest.json
"permissions": [ "tabs", "http://*/*", "https://*/*" ], "background": { "scripts": "js/background.js" }
在 manifest.json 文件中,定义 background 属性。定义的 js/background.js 文件将自动在后台运行。permissions 属性定义了扩展的权限。“tabs”指明可以打开浏览器本身的 tab 标签页,后面两个配置指明浏览器可以向任何 url 发送请求。(不配置会跨域)
- js/background.js
chrome.browserAction.onClicked.addListener(function(){ var url = chrome.extension.getURL("../html/main.html"); window.open(url, "main_page"); })
在 js/background.js 文件中,注册了一个事件函数。当用户点击插件图标时,打开一个 html/main.html 作为新的 tab 页。我们所有的任务都将在这个 tab 页中实现。
- html/main.html
在这个 html 中,载入了三个 js 文件。其中,config/index.js 是测试配置文件。api.conf.dist.js 是入口文件经 webpack 打包后的压缩文件。mocha 之所以独立载入,是因为在浏览器环境中,mocha 必须部署在 window 对象下,所以不能打包进去。核心的逻辑代码,放在 api.conf.dist.js 文件中。
页面中放置了一个按钮和一个 id 为 mocha 的 div。前者点击后会触发 mocha 执行,后者作为一个容器,用于显示测试结果。
- index.js
import {expect} from "chai" import test from "./js/main" function click(e) { mocha.run() document.querySelector("#startBtn").style.display = "none"; } document.addEventListener("DOMContentLoaded", function () { var btn = document.querySelector("#startBtn") btn.addEventListener("click", click); }); mocha && mocha.setup("bdd") mocha.timeout(4000) const mainTest = testCreator(window.apiTestConf) mainTest()
index.js 是打包的入口文件。这里主要做了两件事:
第一,为按钮定义了一个事件监听函数,点击时执行 mocha。
第二,初始化 mocha,设置其执行模式(bdd)和超时时间。
第三,执行 mainTest 函数,在这里定义了所有测试用例。测试用例根据用户配置文件动态,这也是核心逻辑。下面将进一步详述。
下面展示本文使用的测试配置:
window.apiTestConf = { name:"foo", path:"http://baz/japi/platform/", common:{ success: { value: true, type:["boolean"], }, errorCode: { value: [0] } }, publicStruc:[ "results>items", "results" ], apis:{ AccountCenter:{ "110620001":{ name:"获收货地址列表", fullUrl:false, data:{ length:{ min:1 }, item:{ area:{ type:"string" }, areaStr:"string", consignee:"string", detail:"string", id:"number", ifDefault:"boolean", mobile:["string","number"] } } } } } }
测试配置决定了测试用例的组织和测试用例内断言的编写。其结构学习了一些表单验证插件,比表单验证插件复杂的地方在于,表单验证仅仅针对一维的单一字段,而接口返回的数据则是具备一定的嵌套结构的。下面对该配置进行简述。
path:接口地址的公共部分。
common:对接口返回数据的格式上的公共部分进行验证。比如 success 必须为 true,errorcode 必须为 0 等。
publicStruc:接口核心数据部分的路径。系统将根据该路径一步步寻找。如果没找到将会在断言中报错。如果定义了多个路径,则会按顺序从前往后查找。
apis:核心部分,对接口数据进行验证。
AccountCenter:该 key 可变。在这一层级对接口进行分组,比如 AccountCenter 说明下面定义的是账户中心的接口的测试配置。
110620001:该 key 可变。这是真实接口的路径。该 key 值下的对象,就是针对具体数据结构和字段进行配置了。下面将介绍这部分的配置和验证规则,可能会有点枯燥,不感兴趣的读者可以略过不看。
字段验证规则字段验证规则:
字段验证规则指的是接口下data属性下定义的规则。是对接口核心数据的存在与否、数据类型和值所做的规定
基本规则规则
规则仅仅对类型是数字,字符串,布尔值和数组的字段有效,如果要验证的字段是对象,则不起作用。需要对对象内的属性(同样看做字段)做进一步的验证,而不是对对象本身做验证。
验证属性可写的值是对象,数组和字符串,其他类型的值都是非法的
验证属性如果是一个空对象,则直接通过。
验证属性如果是一个数组或字符串,则视为 type 处理
验证属性如果是一个至少包含了required, value, type三者之一的对象,则进行标准化验证
required
验证真实字段必须指定,且必须不为空字符串(也就是说false,0能通过验证。)
value
如果 value 是数组,则验证真实值是否至少等于数组中的某一个值
如果 value 是数字,字符串或者布尔值,验证真实值与其是否相等
如果 value 是一个对象,则进一步判断:
min:规定最小值,仅仅对数字有效
max:规定最大值,仅仅对数字有效
not:不为其值。可以是一个数组,此时不为数组中的所有值
start:开始字符,仅仅对字符串有效
end:结束字符串,仅仅对字符串有效
type
type 可写的值是字符串。这些字符串的可选值为:"number","string","boolean","array"。没有"object","undefined","null",因为没有意义
type 可以是一个数组,数组中的值也是上面的可选值。此时,只要满足数组中的一个值,就能通过验证
length
当数据是数组时才有效,是对数组长度做的验证
min:规定最小值,仅仅对数字有效
max:规定最大值,仅仅对数字有效
item
当数据是数组时才有效,里面进一步规定对数组中每一项的验证
设计完各种规则后,接下来就是依样画葫芦地编写代码了。由于代码量比较多,这里就不再赘述。其核心思想就是根据配置项生成各种测试用例和断言。最后供 mocha 运行,输出结果。
结束和展望本文所设计的方案基本能够满足前端接口测试的要求,但还是有一些缺陷,弊端,及有待改进的地方。
验证规则涉及不够全面和严密,有待丰富,
可以将总体目录结构以及内部的 TestCreater 封装成 npm 包,更方便使用。
测试通过时,无法看到通过了哪些断言。测试失败时,只输出未通过的第一条断言的信息。输出信息不够全面丰富。这一定程度上是 mocha 和 chai本身的特性,不知道有什么方式可以优化。
最后,本文中若出现了错误,或是读者有更好的方案,望不吝啬赐教。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/8750.html
摘要:本文主要关注的是接口测试。所谓接口测试,就是检查系统提供的接口是否符合事先撰写的接口文档。作为接口测试的解决方案,我们必须具备通用性与易用性。 开始 最近几年,前端测试渐渐被人重视,相关的框架和方法已经比较成熟。断言库有should, expect, chai。 单元测试框架有mocha, jasmine, Qunit。 模拟浏览器测试环境有Phantomjs, Slimerjs。 集...
摘要:综合以上问题得出以下结论业务处理失败消息要以的方式向上传递给调用者业务处理失败消息以参数的方式传递不是很适合,并且不能以的方式返回再次思考,最终从里面想到了一点思路幸好是出身。 我需要拍砖 和 看见你们的意见,为团队少挖坑 场景:创建订单 实际流程: 终端调用(PC端、移动端APP、微信端、Web端)-->控制器 或 接口-->实际的业务处理-->控制器 或 接口-...
摘要:,在后续测试时遇到一个诡异,当文件过大时,任务脚本上传到七牛云失败。当我遇到大文件无法上传到七牛云时,断点调试到这里,发现返回的是。后来还真被我找到了,七牛云官方提供一个脚本工具。 业务场景 需求 我们项目有一个文件上传需求,需要从客户端上传到七牛云的对象存储和自己的应用服务器上。这里使用七牛云主要是实现下载分发。应用服务器需要留一份是因为后续需要做文件分析(并且是上传后需要立马分析出...
摘要:将品牌的标价全部加苏南的专栏交流公众号不会对空数组进行检测。方法用于调用数组的每个元素,并将元素传递给回调函数。 showImg(https://segmentfault.com/img/bVblSSO?w=1008&h=298); 前言: 今天我想分享一个有关于循环筛选的知识点,也许是前端小白的你首先想到的是用for循环做筛选,但我这种小菜鸟想到的就是map(工作中很喜欢...
摘要:将品牌的标价全部加苏南的专栏交流公众号不会对空数组进行检测。方法用于调用数组的每个元素,并将元素传递给回调函数。 showImg(https://segmentfault.com/img/bVblSSO?w=1008&h=298); 前言: 今天我想分享一个有关于循环筛选的知识点,也许是前端小白的你首先想到的是用for循环做筛选,但我这种小菜鸟想到的就是map(工作中很喜欢...
阅读 2906·2021-10-19 10:09
阅读 3125·2021-10-09 09:41
阅读 3369·2021-09-26 09:47
阅读 2686·2019-08-30 15:56
阅读 590·2019-08-29 17:04
阅读 979·2019-08-26 11:58
阅读 2504·2019-08-26 11:51
阅读 3352·2019-08-26 11:29