基于node.js的接口自动化测试

为什么要自动化测试

就我个人而言,目前需要测试的有3条产品线,测试范围包括web端、APP端。一轮测试下来,测试所花费的时间是极大的,随之而来问题也就来了:加班测试、版本发布时间受阻、考虑不周全而漏测功能等等。

粗略算了一下,假设半月迭代一次,每次迭代需要5轮测试,人工回归一次就需要5个小时,最终确定一年下来,自动化为你省去600个小时,也就是75个工作日,同时也省去了测试旧功能要吐的烦恼以及人疲惫下产生的错误。

那么在这种情况下,就开始了我的基于node.js自动化测试之旅,所以就有了此篇文章,如有不足之处,还望各位大佬批评指正。

 

Mocha初识

因为后面要用到mocha框架,大家先了解下什么是mocha。

mocha 提供 TDD(测试驱动开发)、BDD (行为驱动开发) 和 exports 风格的接口。

 

BDD风格

BDD是“行为驱动的开发”(Behavior-Driven Development)的简称。BDD认为,不应该针对代码的实现细节写测试,而是要针对行为写测试。BDD测试的是行为,即软件应该怎样运行。

BDD接口提供以下方法:

  • describe():测试套件
  • it():测试用例
  • before():所有测试用例的统一前置动作
  • after():所有测试用例的统一后置动作
  • beforeEach():每个测试用例的前置动作
  • afterEach():每个测试用例的后置动作

BDD的特征就是使用describe()和it() 这两个方法

before()、after()、beforeEach()和afterEach() 是为测试做辅助的作用域,它们合起来组成了hook的概念。

 

describe()和it()

 
describe()

describe()方法接收两个参数:第一个参数是一个字符串,表示测试套件的名字或标题,表示将要测试什么。第二个参数是一个函数,用来实现这个测试套件。

上面引出了一个概念:测试套件。那什么是测试套件呢?

测试套件(test suite)指的是,一组针对软件规格的某个方面的测试用例。也可以看作,对软件的某个方面的描述(describe)。结构如下:

describe("A suite", function() {
    // ...
});
 
it()

要想理解it(),首先我们要知道什么是测试用例? 测试用例(test case)指的是,针对软件一个功能点的测试,是软件测试的最基本单位。一组相关的测试用例,构成一个测试套件。

测试用例由it函数构成,它与describe函数一样,接受两个参数:第一个参数是字符串,表示测试用例的标题;第二个参数是函数,用来实现这个测试用例。

 
BDD风格用例
//模块依赖
var assert = require("assert");

describe('Array', function(){   //测试套件
    describe('#indexOf()', function(){
        it('当值不存在时应该返回 -1', function(){   //测试用例
        assert.equal(-1, [1,2,3].indexOf(5)); //断言条件
        assert.equal(-1, [1,2,3].indexOf(0));
        });
    });
});
 

TDD风格

TDD(测试驱动开发)组织方式是使用测试集(suite)和测试(test)。

每个测试集都有 setup 和 teardown 函数。这些方法会在测试集中的测试执行前执行,它们的作用是为了避免代码重复以及最大限度使得测试之间相互独立。

TDD接口提供以下方法:

  • suite:类似BDD中 describe()
  • test:类似BDD中 it()
  • setup:类似BDD中 before()
  • teardown:类似BDD中 after()
  • suiteSetup:类似BDD中 beforeEach()
  • suiteTeardown:类似BDD中 afterEach()
 

示例

var assert = require("assert");

suite('Array', function(){
    setup(function(){
        console.log('测试执行前执行');
    });

suite('#indexOf()', function(){
    test('当值不存在时应该返回 -1', function(){
        assert.equal(-1, [1,2,3].indexOf(4));
        });
    });
});

运行mocha:

mocha --ui tdd .js (表示的是文件名)

PS:mocha 默认是使用 bdd 的接口,所以在这里我们告诉mocha我们用的是tdd.
 

hook机制

hook 就是在测试流程的不同时段触发,比如在整个测试流程之前,或在每个独立测试之前等。

hook也可以理解为是一些逻辑,通常表现为一个函数或者一些声明,当特定的事件触发时 hook 才执行。

提供方法有:before()、beforeEach() after() 和 afterEach()。

方法解析:

  • before():所有测试用例的统一前置动作
  • after():所有测试用例的统一后置动作
  • beforeEach():每个测试用例的前置动作
  • afterEach():每个测试用例的后置动作

用法:

describe('hooks', function() {
    before(function() {
        //在执行本区块的所有测试之前执行
    });

    after(function() {
        //在执行本区块的所有测试之后执行
    });

    beforeEach(function() {
        //在执行本区块的每个测试之前都执行
    });

    afterEach(function() {
        //在执行本区块的每个测试之后都执行
    });

        //测试用例
});
 

异步测试

Mocha默认每个测试用例最多执行2000毫秒,如果到时没有得到结果,就报错。对于涉及异步操作的测试用例,这个时间往往是不够的,需要用-t或--timeout参数指定超时门槛。

it('测试应该5000毫秒后结束', function (done) {
    let x = true
    let f = function () {
        x = false
        expect(x).to.be.not.ok
        done() // 通知Mocha测试结束
    }
   setTimeout(f, 4000)
})

上面的测试用例,需要4000毫秒之后,才有运行结果。所以,需要用-t或--timeout参数,改变默认的超时设置。

$ mocha -t 5000 timeout.test.js

上面命令将测试的超时时限指定为5000毫秒。

另外,上面的测试用例里面,有一个done函数。it块执行的时候,传入一个done参数,当测试结束的时候,必须显式调用这个函数,告诉Mocha测试结束了。否则,Mocha就无法知道,测试是否结束,会一直等到超时报错。

 

断言

mocha支持任何可以抛出一个错误的断言模块。例如:should.js、better-assert、expect.js、chai等。这些断言库各有各的特点,大家可以了解一下它们的特点,根据使用场景来选择断言库。

 

assert断言(详细说明)

断言(assert)指的是对代码行为的预期。一个测试用例内部,包含一个或多个断言(assert)。

assert断言会返回一个布尔值,表示代码行为是否符合预期。测试用例之中,只要有一个断言为false,这个测试用例就会失败,只有所有断言都为true,测试用例才会通过。

例如

assert.equal(-1, [1,2,3].indexOf(5));
assert.equal(-1, [1,2,3].indexOf(0));

实际值(-1)和期望值([1,2,3].indexOf(5))是一样的,断言为true,所以这个测试用例成功了。

mocha允许开发者使用任意的断言库,当这些断言库抛出了一个错误异常时,mocha将会捕获并进行相应处理。下面是一些适用于Node.js或浏览器的断言库:

  • should.js
  • expect.js
  • chai.js
  • better-assert
  • assert:node.js原生模块。
 

chai断言

Chai 是一个非常灵活的断言库,它可以让你使用如下三种主要断言方式的任何一种:

 
assert:

这是来自老派测试驱动开发的经典的assert方式。比如:

assert.equal(variable, "value");

 
expect:expect语法

这种链式的断言方式在行为驱动开发中最为常见。比如:

expect(variable).to.equal("value");

 
should:

这也是在测试驱动开发中比较常用的方式之一。举例:

variable.should.equal("value");

 

一点技巧

 

仅执行指定测试

大型项目有很多测试用例,有时,我们希望只运行其中的几个,这时可以用.only()方法。describe块和it块都允许调用.only()方法,表示只运行某个测试套件或测试用例。

describe('Array', function(){
    describe.only('#indexOf()', function(){
        ...
    })
})

或一个指定的测试用例:

describe('Array', function(){
    describe('#indexOf()', function(){
        it.only('当值不存在时应该返回 -1', function(){
            ...
        })

        it('当值不存在时应该返回 -1', function(){
            ...
        })
    })
})
 

忽略某个测试

该特性和 .only()非常相似,通过添加 .skip() 你可以告诉 Mocha 忽略的测试套件或者测试用例(可以有多个)。该操作使得这些操作处于挂起的状态,这比使用注释来的要好,因为你可能会忘记把注释给取消掉。

describe('Array', function(){
    describe.skip('#indexOf()', function(){
        ...
    })
})

或一个指定的测试用例:

describe('Array', function(){
    describe('#indexOf()', function(){
        it.skip('当值不存在时应该返回 -1', function(){
            ...
        })

        it('当值不存在时应该返回 -1', function(){
            ...
        })
    })
})
 

环境准备

  • 操作系统:Windows、Mac
  • 代码编辑器:随个人喜好,推荐使用Visual Studio Code
  • node.js 官网:https://nodejs.org/en/
  • 安装配置教程:传送门
 

具体步骤

  • 下载Visual Studio Code,node.js, node.js使用最新版或者稳定版都可以。

  • 安装完成后,打开命令行 输入node --version 或 npm --version 可以看到具体版本即可,例如我本机安装版本为:

    $ node --version

    v8.11.4

    $ npm --version

    5.6.0

  • 建立一个名为exam的项目(文件夹),再建一个为test的下级目录,将编写好的测试文件放到此目录下

  • 模块安装

    • 在项目位置打开命令提示符,或使用VS code自带的终端,输入npm install mocha命令安装mocha,或输入cnpm install mocha、yarn add mocha(也可简写为npm i 模块名/cnpm i 模块名/yarn add 模块名)

    • 继续输入cnpm i或yarn add命令安装其他模块(可能会出现安装失败的情况,需要多装几次)

注:cnpm安装方法npm i cnpm;yran安装方法npm i yarn

全局安装命令:npm install -g mocha

作为项目的依赖安装命令:npm install --save-dev mocha

  • 作为一个新的Node.js项目,先执行npm init 创建 package.json文件,创建时会要求输入项目信息,根据实际情况进行填写即可

  • 在package.json文件中设置一个测试脚本:

    "scripts":{
        "test": "mocha"
    },
    
 

脚本的编写

  • 全局定义(用到什么定义什么)

    /* global describe */
    
    /* global before */
    
    /* global beforeEach */
    
    /* global it */
    
  • 导包、引模块(需要什么导什么)

    let $ = global.$ = require('meeko')
    let assert = require('assert')
    let req = require('co-request')
    const Pro = require('../config')
    const db = global.db = require('j2sql')(Pro.mysql)
    const api = require('../models/api')
    const url = 'http://127.0.0.1:16001'
    const crypto = require('crypto')
    const transSql = require('../models/utils')
    let con = require('./con')
    
  • 这里是测试之前的准备工作(获取token)和部分脚本

    describe('接口测试', async function () {
        before(async function () {
            await $.wait(1500)
    	            let r = (await req['post']({
    	                url: url + '/account/login',
    	                form: {
    	                    unicode: con.unicode // 微信登录唯一id
    	                }
    	            })).body
    	            r = JSON.parse(r)
    	            // console.log(r)
    	            token = r.data.token
    	            timestamp = +new Date()
    	            sign = signHM(timestamp, token)
    	        })
    	        beforeEach(async function () {
    	            await $.wait(50)
        })
    
        // 这是登录接口的部分脚本
        describe('/account/login          post  微信登录游戏', async function () {
            it('登录失败,缺少unicode', async function () {
                let r = (await req['post']({
                url: url + '/account/login',
                form: {
                    unicode: '' // 微信登录唯一id
                }
                })).body
                r = JSON.parse(r)
                // console.log(r)
                assert.strictEqual(r.msg, '登录失败')
            })
            it('登录失败,unicode类型错误', async function () {
                let r = (await req['post']({
                url: url + '/account/login',
                form: {
                    unicode: con.typeErr // 类型错误
                }
                })).body
                r = JSON.parse(r)
                // console.log(r)
                assert.strictEqual(r.msg, '登录失败')
            })
            it('登录失败,方法类型不对', async function () {
                let r = (await req['get']({
                url: url + '/account/login',
                form: {
                    unicode: '' // 微信登录唯一id
                }
                })).body
                r = JSON.parse(r)
                // console.log(r)
                assert.strictEqual(r.msg, '方法类型不对')
            })
        })
    })
    

在上面的脚本中,使用了BDD的方式来进行测试,即describe()测试套件 + it()测试用例 + assert断言方式。比较简单,还望各位大佬多多指教。

 

运行

如果在package.json文件中设置测试脚本,那么我们直接在终端输入npm test即可运行(如出现缺少模块的情况,按提示一个一个安装即可)

 

测试报告

  • 如果想查看测试报告,先在“扩展”中搜索“Live Server”并添加

  • 使用命令npm install --save-dev mochawesome安装 mochawesome,然后在package.json文件中添加:

    "scripts": {
        "test": "mocha --reporter mochawesome"
    },
    
  • 最后打开mochawesome-report文件夹,右击mochawesome.html选择Open with Live Server

 

持续集成

我们将测试脚本写好后,通过gitlab配置钩子,将代码上传到gitlab后,就能实现自动化测试了

 

几个需要注意的文件

 

config.js

配置文件,包括了本地开发环境、测试环境和生产环境

环境里包含了数据库、Redis、微信公众号、文件上传下载等等等等。

 

package.json

 

devDependencies 和 dependencies

dependencies 存放项目或组件代码中依赖到的

devDependencies 存放测试代码依赖的包或构建工具的包

npm install 【依赖】或 npm install 【依赖】--save会把依赖放到dependencies下,表示代码运行时所需要的包。

npm install 【依赖】--save-dev 会把依赖放到devDependencies下,表示开发时依赖的插件(即不会打包至线上)。

 
安装依赖
  • 如果拿到别人的项目,需要安装之前package.json中devDependencies 和 dependencies两个模块下所列举的依赖,可以通过执行以下命令实现

    npm install
    
  • 如果拿到别人的项目,只需要安装之前package.json中dependencies 模块下所列举的依赖,可以通过执行以下命令实现

    npm install packagename
    
  • 如果拿到别人的项目,只需要安装之前package.json中devdependencies 模块下所列举的依赖,可以通过执行以下命令实现

    npm install packagename -dev
    
 
删除依赖
  • npm uninstall "依赖名称":删除依赖,但不会删除package.json的配置(即通过npm install依然可以安装该依赖),删除mocha依赖实例代码如下

    npm uninstall mocha
    
  • npm uninstall "依赖名称"  --save-dev:删除依赖,同时删除package.json中devdependencies 的配置,删除mocha依赖实例代码如下

    npm uninstall mocha  --save-dev
    
  • npm uninstall "依赖名称"  --save:删除依赖,同时删除package.json中dependencies 的配置,删除mocha依赖实例代码如下

    npm uninstall mocha --save
posted @ 2019-05-30 16:34  leo呀  阅读(5647)  评论(0编辑  收藏  举报