接口测试 测试来带个节奏之 API 测试工具篇

大大灰灰狼 · 2016年07月11日 · 最后由 大大灰灰狼 回复于 2016年12月07日 · 466 次阅读
本帖已被设为精华帖!

目组里面的 e2e 测试运行多年,历经了经常会出现各种莫名其妙的环境问题、运行变慢等问题后,项目组终于决定引入 API 功能测试。同时可以在尽量保证测试覆盖率的前提下把重复测试的 e2e 测试脚本清理掉,提高持续集成效率 (策略参考测试金字塔)。

那么问题来了,做 API 功能测试如何选择工具勒?API 功能测试可以通过 soapUI 或者 postman 等带 GUI 的工具简单录制脚本执行,也可以通过开源项目工具自己写代码完成。根据项目的实际情况,这里我们选择使用后者,便于定制和持续集成。

工具选择

目前市面上比较流行的 API 测试开源框架有很多。首先能够想到的就是REST-assured。Rest-Assured 是一套由 Java 实现的 REST API 测试框架,它是一个轻量级的 REST API 客户端,可以直接编写代码向服务器端发起 HTTP 请求,并验证返回结果。官方的介绍是:

Testing and validation of REST services in Java is harder than in dynamic languages such as Ruby and Groovy. REST Assured brings the simplicity of using these languages into the Java domain.

打开 github 提交记录,发现这个框架最近还有人在持续提交代码,说明维护的还不错,列为备选项目。

另外经过各种途径了解到目前还有一套非常流行的,由大神 tj 等人开发的 nodeJS 测试框架supertest。这是一套脱胎于著名的superagent的 API 测试框架,官方的说法是:

Super-agent driven library for testing node.js HTTP servers using a fluent API
HTTP assertions made easy via superagent.

稍微对比一下这两个工具,从几个方面来考虑取舍:

  1. 项目代码基于 Java,同时也有 NodeJS 代码在里面,从环境上来讲两个工具都不需要再额外配置。这点两者打个平手。
  2. 学习成本方面,两个工具都可以方便的从网上搜出一大堆学习资料,而且官方给的资料也比较全。又是平手。
  3. 维护成本上讲,supertest 是基于动态语言,不需要浪费编译时间;万一写错了代码立马改完立马重新跑起来。而且官网上号称"SuperTest works with any test framework"可扩展性貌似也比较强。
  4. 从可移植性上看,supertest 由于使用 nodeJS,理论上如果框架做的够好,只要有 node,就可以把同一套脚本丢到各种不同的地方运行。
  5. 最后再对比下易用性。安装方面,REST-assured 通常会借助如 maven、grade 之类的工具安装,配置运行环境比较麻烦。而 superset 只需要简单的一行 npm install 命令安装后即可使用。考虑到我比较懒,supertest 完胜,就酱。

开始入坑

开始学习 supertest。
首先打开它的github,了解 supertest 几个关键信息:

  • 继承了superagent所有的 API 和用法。
  • 使用前需安装node,然后用npm install supertest --save-dev或者cnpm install supertest --save-dev安装 supertest。
  • 和 superagent 一样,需要通过调用.end() 执行一个 request 请求。
  • 调用.expect()来做断言,如果在里面填入数字,默认是检查 http 请求返回的状态码;

完了我们来分析下官方示例代码,然后仿造它来撸一段代码试试看。

var request = require('supertest');
var express = require('express');
var app = express();

这里的 app 目测只是用来做一个 mock server,跟 supertest 有关的测试只有下面这部分

request(app) 
    .get('/user') 
    .expect('Content-Type', /json/) 
    .expect('Content-Length', '15') 
    .expect(200) 
    .end(function(err, res) { 
        if (err) throw err; 
    });

分析这段测试代码,首先是用request(app)实例化一个 server,然后是.expect()分别验证了 response header 里面的 content-type,content-length 和 response 的 http status 是否 200. 这就是 supertest 的基本写法了。

小试牛刀

我们用全球最大的同性交友平台 github 来做个实验,设计一个判断是否成功进入首页的用例。

准备工作:使用你的 chrome,打开 develop tools 的 Network 标签,先看看进入 github 首页时上有哪些请求,记录下进入首页的请求,找到这个请求的 URL,Method 等关键信息。

请求

实施阶段:我们再随便打开个 vim,记事本什么的文本编辑工具撸一小段代码试试刀:

var request = require('supertest')('https://github.com/');

request
    .get('/')
    .expect(2010)
    .end(function(err, res) {
            if (err) throw err;
    });

保存下来,命名个 test.js 什么的,然后运行它

node test.js

然后你发现得到这个提示异常的结果

执行结果
这说明我们的断言成功了!把.expect(2010)改成实际会返回的.expect(200)再试试看,没有返回异常结果说明测试通过了!

优化一下:虽然测试成功了,但是这个测试结果的可读性实在是有些令人不甚满意,尤其是测试成功了连个提示都没有。

于是我们考虑用官网例子中提到的测试框架 Mocha 来优化下这个测试。

Mocha是一个优秀的 JavaScript 测试框架,长得跟Jasmine一个样。官网上的介绍是:

Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases. Hosted on GitHub.

这个框架提供了各种 style 的测试报告。结合 supertest 使用,可以让我们的 API 测试报告可视化上一个档次。

顺便可以加上个常用的 post 请求的测试:

var request = require('supertest')('https://github.com');

describe('Github home page',function(){

  this.timeout(10000);

  before('must be on home page',function(done){
    request.get('/')
      .expect(200,done);
  });

  it('could be navigated to register page',function(done){
    request.get('/join')
      .expect(200,done);
  });

  it('will refuse the request if username has been taken',function(done){
    request.post('/signup_check/username')
      .type('form')
      .send('value=lala')
      .expect(404)
      .end(function(err,res){
        if (err) return done(err)
        done();
      })
  });
});

这个测试比起刚才的版本更加具有可读性,借助 Mocha 框架,每段测试之前都有一个描述信息,一看就知道你这段代码在测试什么。

其中before()是 Mocha 提供的 hook,相当于 beforeAll,会在所有测试前执行。其他 hook 还有会在所有测试执行之后执行的after(),会在每个测试前都执行一遍的beforeEach()和会在每一个测试执行之后都执行一遍的afterEach()。hook 用在清理测试数据方面会非常方便。

然后describe()描述了是测试的是什么东西:

    describe('描述测试对象',function(){
      //测试用例
    })

`describe()`里面的`it()`则描述了具体的测试用例

    it('描述测试用例', function(done){
      //测试用例实现
      done();
    })

done()是 Mocha 提供的回调方法,如果没有done()的话 Javascript 回一直等待回调致超时。顺带提一下 Mocha 的默认超时时间是 2 秒,所以在 describe 的下面加上this.timeout(10000);把超时时间重新设置为 10 秒。

需要注意的是在虽然使用 Mocha 的时候可以忽略 superset 的.end(),而直接在.expect()添加 done 参数,例如.expect(200,done)。但是如果使用了.end()的写法的话,仍然需要在.end()块儿中调用done()

最后个用例中的.send('value=lala')是 post 的 request body,通过.type()来指定类型。.type()在缺省状态下默认是 JSON(详见superagent 源代码),本例中使用的是 form 类型。 当然,也可以不用 send() 而是选择直接在 post 的 url 中加上参数request.post('/signup_check/username?value=lala'),但是如果要参数化的话,还是推荐用.send()

Mocha 还提供了 watch 功能,使用带参数的命令mocha -w 测试脚本.js来监视测试脚本,当脚本有变化的时候 Mocha 会自动运行脚本。

测试结果如下:

测试不通过

更新最后一个用例中的.expect(404).expect(403),测试通过。

测试通过

现在不管是测试代码的可读性还是测试报告的可读性,都比之前强多了。而且还可以使用--reporter参数让测试报告变成各种形状,比如
nyan

查漏补缺:总算是解决了代码可读性和测试报告的问题。再回过头来看看整个 demo,突然发现调研了这么半天,竟然忽略了在很多业务场景中,调用 API 需要验证用户是否登录的问题。换句话说,需要在不同的 http 请求中保持 cookie。

幸好 supertest 提供了这个解决方案,使用 supertest 的 agent 功能来解决这个问题。

    var request = require('superset')

    describe('测试cookie', function(){

      var agent = request.agent('待测server');
 
      it('should save cookies', function(done){
        agent
        .get('/')
        .expect('set-cookie', 'cookie=hey; Path=/', done);
      })
 
      it('should send cookies', function(done){
        agent
        .get('/return')
        .expect('hey', done);
      })
    })

可以看到第一个用例是测试cookie=hey,而到了第二个测试里面,由于被测实例由单纯的"request"变成了"request.agent()",所以 cookie “hey” 被 agent 带入到了第二个用例中,当访问"/return"的时候不用再重新 set cookies 了。

另外我们也可以通过在每次请求前去 set cookie 的方法达到同样的效果。

.set('Cookie', 'a cookie string') 

最后如果是要测试授权资源的话,superagent 也提供了.auth()方法去获取授权。

request .get('http://local') 
    .auth('tobi', 'learnboost') .
end(callback);

现在看上去调研工作算是差不多了,能够满足大部分的测试场景。接下来只需要再设计下测试代码结构,抽象下公共组件,做下参数化,分离下测试数据就搞定了。可是细想下,如果需要写了一大堆测试的话,难道要挨个去执行 mocha xxx 脚本的命令来跑测试?

还好项组目已经在用grunt 构建工具。谷歌一下发现有一个 grunt 插件 “grunt-mocha-test” 貌似挺不错的。按照它的说明文档,只需要在 grunt 配置文件里面加上一段

grunt-mocha-test配置
其中 reporter 就是制定报告的格式, src 就是需要执行的脚本的路径,*.js指定执行全部 js 格式的文件。

最后再注册一个 grunt 命令,比如:

grunt.registerTask('apitest', 'mochaTest');

就能简单的在命令行中使用

grunt apitest

来执行所有的测试文件了。这样也可以方便的在 Jenkins 中配置一个新的测试任务,加入持续集成。

至此,工具选型全部完成,核心是 supertest,包装是 mocha,执行用 grunt,收工。

总结一下

总结一下,在工具选型的时候,建议考虑这些方面:

  • 结合项目技术栈使用
  • 新工具学习成本、维护成本、可扩展性
  • 是否可以简单实现代码满足所有业务场景,比如非 REST 风格的 API,或者一些特殊场景
  • 代码易读,测试报告可视化
  • 脚本执行简单
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 13 条回复 时间 点赞

测试来带个节奏?撒意思

#1 楼 @pacerron "测试的,你来带下项目节奏"

陈恒捷 将本帖设为了精华贴 07月11日 12:31

不错,super test 这个框架我也没用过,有空试试~

感谢分享,多了一种思路

大大灰灰狼 [该话题已被删除] 中提及了此贴 07月11日 18:59

看到了 Node.js,带感了,哈哈

"全球最大的同性交友平台 github"💂 😹 😹 😹 😹

这个可以好好收藏,看下

如果想先调一下接口 看一下结果还是需要自己写代码? 测试还要写代码。。 断言还需要写代码,不麻烦吗?真的跟自己写代码来测接口没区别了。。API 的测试工具应该是 方便使用调用,断言,以及出报告,而不需要自己写 1 句代码。。

赞,这种分享看的真舒服

没有断言数据的吗?

数据库

#14 楼 @kaixin 你是说在测试某个接口返回值的同时校验数据库里面数据有没有更新?我不知道有没有理解错你的意思。supertest 本质上是一个及轻量级的 http client,所以能做的校验也就只是发的 http 请求和 response 而已。如果需要校验数据库的话,有一些其他方法可以尝试下,比如用 nodejs 直接连数据库 (e.g. node-sqlserver),查询库里面数据后校验结果;又或者是用其他的可以获取到数据库的值的请求去校验返回结果。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册