基于node.js的接口自動(dòng)化測(cè)試

# 基于node.js的接口自動(dòng)化測(cè)試

## 為什么要自動(dòng)化測(cè)試

就我個(gè)人而言,目前需要測(cè)試的有3條產(chǎn)品線,測(cè)試范圍包括H5、Android、iOS端。一輪測(cè)試下來,測(cè)試所花費(fèi)的時(shí)間是極大的,隨之而來問題也就來了:加班測(cè)試、版本發(fā)布時(shí)間受阻、考慮不周全而漏測(cè)功能等等。

粗略算了一下,假設(shè)半月迭代一次,每次迭代需要5輪測(cè)試,人工回歸一次就需要5個(gè)小時(shí),最終確定一年下來,自動(dòng)化為你省去600個(gè)小時(shí),也就是75個(gè)工作日,同時(shí)也省去了測(cè)試舊功能要吐的煩惱以及人疲憊下產(chǎn)生的錯(cuò)誤。

那么在這種情況下(sky的bi po下,蟹蟹sky),就開始了我的基于node.js自動(dòng)化測(cè)試之旅,所以就有了此篇文章,如有不足之處,還望各位測(cè)試大佬批評(píng)指正。

## Mocha初識(shí)

因?yàn)楹竺嬉玫絤ocha框架,大家先了解下什么是mocha。

mocha 提供 TDD(測(cè)試驅(qū)動(dòng)開發(fā))、BDD (行為驅(qū)動(dòng)開發(fā)) 和 exports 風(fēng)格的接口。

### BDD風(fēng)格

BDD是“行為驅(qū)動(dòng)的開發(fā)”(Behavior-Driven Development)的簡(jiǎn)稱。BDD認(rèn)為,不應(yīng)該針對(duì)代碼的實(shí)現(xiàn)細(xì)節(jié)寫測(cè)試,而是要針對(duì)行為寫測(cè)試。BDD測(cè)試的是行為,即軟件應(yīng)該怎樣運(yùn)行。

BDD接口提供以下方法:

1.describe():測(cè)試套件

-?it():測(cè)試用例

-?before():所有測(cè)試用例的統(tǒng)一前置動(dòng)作

-?after():所有測(cè)試用例的統(tǒng)一后置動(dòng)作

-?beforeEach():每個(gè)測(cè)試用例的前置動(dòng)作

-?afterEach():每個(gè)測(cè)試用例的后置動(dòng)作

BDD的特征就是使用describe()和it() 這兩個(gè)方法

before()、after()、beforeEach()和afterEach() 是為測(cè)試做輔助的作用域,它們合起來組成了hook的概念。

#### describe()和it()

##### describe()

describe()方法接收兩個(gè)參數(shù):第一個(gè)參數(shù)是一個(gè)字符串,表示測(cè)試套件的名字或標(biāo)題,表示將要測(cè)試什么。第二個(gè)參數(shù)是一個(gè)函數(shù),用來實(shí)現(xiàn)這個(gè)測(cè)試套件。

上面引出了一個(gè)概念:測(cè)試套件。那什么是測(cè)試套件呢?

測(cè)試套件(test suite)指的是,一組針對(duì)軟件規(guī)格的某個(gè)方面的測(cè)試用例。也可以看作,對(duì)軟件的某個(gè)方面的描述(describe)。結(jié)構(gòu)如下:

????describe("A suite", function() {

????????// ...

????});

##### it()

要想理解it(),首先我們要知道什么是測(cè)試用例? 測(cè)試用例(test case)指的是,針對(duì)軟件一個(gè)功能點(diǎn)的測(cè)試,是軟件測(cè)試的最基本單位。一組相關(guān)的測(cè)試用例,構(gòu)成一個(gè)測(cè)試套件。

測(cè)試用例由it函數(shù)構(gòu)成,它與describe函數(shù)一樣,接受兩個(gè)參數(shù):第一個(gè)參數(shù)是字符串,表示測(cè)試用例的標(biāo)題;第二個(gè)參數(shù)是函數(shù),用來實(shí)現(xiàn)這個(gè)測(cè)試用例。

##### BDD風(fēng)格用例

????//模塊依賴

????var assert = require("assert");

????describe('Array', function(){???//測(cè)試套件

????????describe('#indexOf()', function(){

????????????it('當(dāng)值不存在時(shí)應(yīng)該返回 -1', function(){???//測(cè)試用例

??????????????assert.equal(-1, [1,2,3].indexOf(5)); //斷言條件

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

????????????});

????????});

????});

### TDD風(fēng)格

TDD(測(cè)試驅(qū)動(dòng)開發(fā))組織方式是使用測(cè)試集(suite)和測(cè)試(test)。

每個(gè)測(cè)試集都有 setup 和 teardown 函數(shù)。這些方法會(huì)在測(cè)試集中的測(cè)試執(zhí)行前執(zhí)行,它們的作用是為了避免代碼重復(fù)以及最大限度使得測(cè)試之間相互獨(dú)立。

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('測(cè)試執(zhí)行前執(zhí)行');

????????});

????suite('#indexOf()', function(){

????????test('當(dāng)值不存在時(shí)應(yīng)該返回 -1', function(){

????????????assert.equal(-1, [1,2,3].indexOf(4));

????????????});

????????});

????});

運(yùn)行mocha:

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

????PS:mocha 默認(rèn)是使用 bdd 的接口,所以在這里我們告訴mocha我們用的是tdd.

### hook機(jī)制

hook 就是在測(cè)試流程的不同時(shí)段觸發(fā),比如在整個(gè)測(cè)試流程之前,或在每個(gè)獨(dú)立測(cè)試之前等。

hook也可以理解為是一些邏輯,通常表現(xiàn)為一個(gè)函數(shù)或者一些聲明,當(dāng)特定的事件觸發(fā)時(shí) hook 才執(zhí)行。

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

方法解析:

-?before():所有測(cè)試用例的統(tǒng)一前置動(dòng)作

-?after():所有測(cè)試用例的統(tǒng)一后置動(dòng)作

-?beforeEach():每個(gè)測(cè)試用例的前置動(dòng)作

-?afterEach():每個(gè)測(cè)試用例的后置動(dòng)作

用法:

????describe('hooks', function() {

????????before(function() {

????????????//在執(zhí)行本區(qū)塊的所有測(cè)試之前執(zhí)行

????????});

????????after(function() {

????????????//在執(zhí)行本區(qū)塊的所有測(cè)試之后執(zhí)行

????????});

????????beforeEach(function() {

????????????//在執(zhí)行本區(qū)塊的每個(gè)測(cè)試之前都執(zhí)行

????????});

????????afterEach(function() {

????????????//在執(zhí)行本區(qū)塊的每個(gè)測(cè)試之后都執(zhí)行

????????});

????????????//測(cè)試用例

????});

### 異步測(cè)試

Mocha默認(rèn)每個(gè)測(cè)試用例最多執(zhí)行2000毫秒,如果到時(shí)沒有得到結(jié)果,就報(bào)錯(cuò)。對(duì)于涉及異步操作的測(cè)試用例,這個(gè)時(shí)間往往是不夠的,需要用-t或--timeout參數(shù)指定超時(shí)門檻。

????it('測(cè)試應(yīng)該5000毫秒后結(jié)束', function (done) {

????????let x = true

????????let f = function () {

????????????x = false

????????????expect(x).to.be.not.ok

????????????done() // 通知Mocha測(cè)試結(jié)束

????????}

???????setTimeout(f, 4000)

????})

上面的測(cè)試用例,需要4000毫秒之后,才有運(yùn)行結(jié)果。所以,需要用-t或--timeout參數(shù),改變默認(rèn)的超時(shí)設(shè)置。

????$ mocha -t 5000 timeout.test.js

上面命令將測(cè)試的超時(shí)時(shí)限指定為5000毫秒。

另外,上面的測(cè)試用例里面,有一個(gè)done函數(shù)。it塊執(zhí)行的時(shí)候,傳入一個(gè)done參數(shù),當(dāng)測(cè)試結(jié)束的時(shí)候,必須顯式調(diào)用這個(gè)函數(shù),告訴Mocha測(cè)試結(jié)束了。否則,Mocha就無法知道,測(cè)試是否結(jié)束,會(huì)一直等到超時(shí)報(bào)錯(cuò)。

### 斷言

mocha支持任何可以拋出一個(gè)錯(cuò)誤的斷言模塊。例如:should.js、better-assert、expect.js、chai等。這些斷言庫(kù)各有各的特點(diǎn),大家可以了解一下它們的特點(diǎn),根據(jù)使用場(chǎng)景來選擇斷言庫(kù)。

#### assert斷言[(詳細(xì)說明)](https://www.runoob.com/nodejs/nodejs-install-setup.html)

斷言(assert)指的是對(duì)代碼行為的預(yù)期。一個(gè)測(cè)試用例內(nèi)部,包含一個(gè)或多個(gè)斷言(assert)。

assert斷言會(huì)返回一個(gè)布爾值,表示代碼行為是否符合預(yù)期。測(cè)試用例之中,只要有一個(gè)斷言為false,這個(gè)測(cè)試用例就會(huì)失敗,只有所有斷言都為true,測(cè)試用例才會(huì)通過。

例如

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

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

實(shí)際值(-1)和期望值([1,2,3].indexOf(5))是一樣的,斷言為true,所以這個(gè)測(cè)試用例成功了。

mocha允許開發(fā)者使用任意的斷言庫(kù),當(dāng)這些斷言庫(kù)拋出了一個(gè)錯(cuò)誤異常時(shí),mocha將會(huì)捕獲并進(jìn)行相應(yīng)處理。下面是一些適用于Node.js或?yàn)g覽器的斷言庫(kù):

-?should.js

-?expect.js

-?chai.js

-?better-assert

-?assert:node.js原生模塊。

#### chai斷言

Chai 是一個(gè)非常靈活的斷言庫(kù),它可以讓你使用如下三種主要斷言方式的任何一種:

##### assert:

這是來自老派測(cè)試驅(qū)動(dòng)開發(fā)的經(jīng)典的assert方式。比如:

`assert.equal(variable, "value");`

##### expect:[expect語(yǔ)法](http://cw.hubwiz.com/card/c/562085141bc20c980538e25a/1/1/5/)

這種鏈?zhǔn)降臄嘌苑绞皆谛袨轵?qū)動(dòng)開發(fā)中最為常見。比如:

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

##### should:

這也是在測(cè)試驅(qū)動(dòng)開發(fā)中比較常用的方式之一。舉例:

`variable.should.equal("value");`

### 一點(diǎn)技巧

#### 僅執(zhí)行指定測(cè)試

大型項(xiàng)目有很多測(cè)試用例,有時(shí),我們希望只運(yùn)行其中的幾個(gè),這時(shí)可以用.only()方法。describe塊和it塊都允許調(diào)用.only()方法,表示只運(yùn)行某個(gè)測(cè)試套件或測(cè)試用例。

????describe('Array', function(){

????????describe.only('#indexOf()', function(){

????????????...

????????})

????})

或一個(gè)指定的測(cè)試用例:

????describe('Array', function(){

????????describe('#indexOf()', function(){

????????????it.only('當(dāng)值不存在時(shí)應(yīng)該返回 -1', function(){

????????????????...

????????????})

????????????it('當(dāng)值不存在時(shí)應(yīng)該返回 -1', function(){

????????????????...

????????????})

????????})

????})

#### 忽略某個(gè)測(cè)試

該特性和 .only()非常相似,通過添加 .skip() 你可以告訴 Mocha 忽略的測(cè)試套件或者測(cè)試用例(可以有多個(gè))。該操作使得這些操作處于掛起的狀態(tài),這比使用注釋來的要好,因?yàn)槟憧赡軙?huì)忘記把注釋給取消掉。

????describe('Array', function(){

????????describe.skip('#indexOf()', function(){

????????????...

????????})

????})

或一個(gè)指定的測(cè)試用例:

????describe('Array', function(){

????????describe('#indexOf()', function(){

????????????it.skip('當(dāng)值不存在時(shí)應(yīng)該返回 -1', function(){

????????????????...

????????????})

????????????it('當(dāng)值不存在時(shí)應(yīng)該返回 -1', function(){

????????????????...

????????????})

????????})

????})

## 環(huán)境準(zhǔn)備

-?操作系統(tǒng):Windows7,Windows8,Windows10,Mac

-?代碼編輯器:隨個(gè)人喜好,推薦使用Visual Studio Code

-?node.js 官網(wǎng):[https://nodejs.org/en/](https://nodejs.org/en/)

-?安裝配置教程:[傳送門](https://www.runoob.com/nodejs/nodejs-install-setup.html)

## 具體步驟

-?下載Visual Studio Code,node.js, node.js使用最新版或者穩(wěn)定版都可以。

-?安裝完成后,打開命令行 輸入node --version 或 npm --version 可以看到具體版本即可,例如我本機(jī)安裝版本為:

????$ node --version

????v8.11.4

????$ npm --version

5.6.0

-?建立一個(gè)名為exam的項(xiàng)目(文件夾),再建一個(gè)為test的下級(jí)目錄,將編寫好的測(cè)試文件放到此目錄下

-?模塊安裝

??-?在項(xiàng)目位置打開命令提示符,或使用VS code自帶的終端,輸入npm install mocha命令安裝mocha,或輸入cnpm install mocha、yarn add mocha(也可簡(jiǎn)寫為npm i 模塊名/cnpm i 模塊名/yarn add 模塊名)

??-?繼續(xù)輸入cnpm i或yarn add命令安裝其他模塊(可能會(huì)出現(xiàn)安裝失敗的情況,需要多裝幾次)

>注:cnpm安裝方法npm i cnpm;yran安裝方法npm i yarn

>

>全局安裝命令:npm install -g mocha

>

>作為項(xiàng)目的依賴安裝命令:npm install --save-dev mocha

-?作為一個(gè)新的Node.js項(xiàng)目,先執(zhí)行npm init 創(chuàng)建 package.json文件,創(chuàng)建時(shí)會(huì)要求輸入項(xiàng)目信息,根據(jù)實(shí)際情況進(jìn)行填寫即可

![](https://i.imgur.com/2Xwh9vC.jpg)

![](https://i.imgur.com/D7GNnKR.png)

-?在package.json文件中設(shè)置一個(gè)測(cè)試腳本:

????????"scripts":{

????????????"test": "mocha"

????????},

## 腳本的編寫

-?全局定義(用到什么定義什么)

????????/* global describe */

????????/* global before */

????????/* global beforeEach */

????????/* global it */

-?導(dǎo)包、引模塊(需要什么導(dǎo)什么)


????????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')

-?這里是測(cè)試之前的準(zhǔn)備工作(獲取token)和部分腳本

????????describe('接口測(cè)試', 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類型錯(cuò)誤', async function () {

????????????????????let r = (await req['post']({

????????????????????url: url + '/account/login',

????????????????????form: {

????????????????????????unicode: con.typeErr // 類型錯(cuò)誤

????????????????????}

????????????????????})).body

????????????????????r = JSON.parse(r)

????????????????????// console.log(r)

????????????????????assert.strictEqual(r.msg, '登錄失敗')

????????????????})

????????????????it('登錄失敗,方法類型不對(duì)', 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, '方法類型不對(duì)')

????????????????})

????????????})

????????})

在上面的腳本中,使用了BDD的方式來進(jìn)行測(cè)試,即describe()測(cè)試套件 + it()測(cè)試用例 + assert斷言方式。比較簡(jiǎn)單,還望各位大佬多多指教。

## 運(yùn)行

如果在package.json文件中設(shè)置測(cè)試腳本,那么我們直接在終端輸入npm test即可運(yùn)行(如出現(xiàn)缺少模塊的情況,按提示一個(gè)一個(gè)安裝即可)

## 測(cè)試報(bào)告

-?如果想查看測(cè)試報(bào)告,先在“擴(kuò)展”中搜索“Live Server”并添加

![](https://i.imgur.com/4yabnzq.png)

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

????????"scripts": {

????????????"test": "mocha --reporter mochawesome"

????????},

-?最后打開mochawesome-report文件夾,右擊mochawesome.html選擇Open with Live Server

![](https://i.imgur.com/bROQZwr.png)

![](https://i.imgur.com/y30IHhm.png)

## 持續(xù)集成

我們將測(cè)試腳本寫好后,通過gitlab配置鉤子,將代碼上傳到gitlab后,就能實(shí)現(xiàn)自動(dòng)化測(cè)試了(由于gitlab沒有權(quán)限,所以沒有g(shù)itlab配置的截圖)

![](https://i.imgur.com/zqGppU7.png)

## 幾個(gè)需要注意的文件

### config.js

配置文件,包括了本地開發(fā)環(huán)境、測(cè)試環(huán)境和生產(chǎn)環(huán)境

環(huán)境里包含了數(shù)據(jù)庫(kù)、Redis、微信公眾號(hào)、文件上傳下載等等等等。

### package.json

#### devDependencies 和 dependencies

dependencies 存放項(xiàng)目或組件代碼中依賴到的

devDependencies 存放測(cè)試代碼依賴的包或構(gòu)建工具的包

npm install 【依賴】或 npm install 【依賴】--save會(huì)把依賴放到dependencies下,表示代碼運(yùn)行時(shí)所需要的包。

npm install 【依賴】--save-dev 會(huì)把依賴放到devDependencies下,表示開發(fā)時(shí)依賴的插件(即不會(huì)打包至線上)。

##### 安裝依賴

-?如果拿到別人的項(xiàng)目,需要安裝之前package.json中devDependencies 和 dependencies兩個(gè)模塊下所列舉的依賴,可以通過執(zhí)行以下命令實(shí)現(xiàn)

????????npm install

-?如果拿到別人的項(xiàng)目,只需要安裝之前package.json中dependencies 模塊下所列舉的依賴,可以通過執(zhí)行以下命令實(shí)現(xiàn)

????????npm install packagename

-?如果拿到別人的項(xiàng)目,只需要安裝之前package.json中devdependencies 模塊下所列舉的依賴,可以通過執(zhí)行以下命令實(shí)現(xiàn)

????????npm install packagename -dev

##### 刪除依賴

-?npm uninstall "依賴名稱":刪除依賴,但不會(huì)刪除package.json的配置(即通過npm install依然可以安裝該依賴),刪除mocha依賴實(shí)例代碼如下

????????npm uninstall mocha

-?npm uninstall "依賴名稱"? --save-dev:刪除依賴,同時(shí)刪除package.json中devdependencies?的配置,刪除mocha依賴實(shí)例代碼如下

????????npm uninstall mocha??--save-dev

-?npm uninstall "依賴名稱"? --save:刪除依賴,同時(shí)刪除package.json中dependencies?的配置,刪除mocha依賴實(shí)例代碼如下

????????npm uninstall mocha --save

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容