mocha


如果你听说过“测试驱动开发”(TDD:Test-Driven Development),单元测试就不陌生。

单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。

比如对函数abs(),我们可以编写出以下几个测试用例:

输入正数,比如1、1.2、0.99,期待返回值与输入相同;

输入负数,比如-1、-1.2、-0.99,期待返回值与输入相反;

输入0,期待返回0;

输入非数值类型,比如null、[]、{},期待抛出Error。

把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。

如果单元测试通过,说明我们测试的这个函数能够正常工作。如果单元测试不通过,要么函数有bug,要么测试条件输入不正确,总之,需要修复使单元测试能够通过。

单元测试通过后有什么意义呢?如果我们对abs()函数代码做了修改,只需要再跑一遍单元测试,如果通过,说明我们的修改不会对abs()函数原有的行为造成影响,如果测试不通过,说明我们的修改与原有行为不一致,要么修改代码,要么修改测试。

这种以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合我们设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍然是正确的。

mocha

mocha是JavaScript的一种单元测试框架,既可以在浏览器环境下运行,也可以在Node.js环境下运行。

使用mocha,我们就只需要专注于编写单元测试本身,然后,让mocha去自动运行所有的测试,并给出测试结果。

mocha的特点主要有:

  1. 既可以测试简单的JavaScript函数,又可以测试异步代码,因为异步是JavaScript的特性之一;

  2. 可以自动运行所有测试,也可以只运行特定的测试;

  3. 可以支持before、after、beforeEach和afterEach来编写初始化代码。

假设我们编写了一个hello.js,并且输出一个简单的求和函数:

// hello.js

module.exports = function (...rest) {
var sum = 0;
for (let n of rest) {
sum += n;
}
return sum;
};

这个函数非常简单,就是对输入的任意参数求和并返回结果。

如果我们想对这个函数进行测试,可以写一个test.js,然后使用Node.js提供的assert模块进行断言:

// test.js

const assert = require('assert');
const sum = require('./hello'); assert.strictEqual(sum(), 0);
assert.strictEqual(sum(1), 1);
assert.strictEqual(sum(1, 2), 3);
assert.strictEqual(sum(1, 2, 3), 6);

assert模块非常简单,它断言一个表达式为true。如果断言失败,就抛出Error。可以在Node.js文档中查看assert模块的所有API

单独写一个test.js的缺点是没法自动运行测试,而且,如果第一个assert报错,后面的测试也执行不了了。

如果有很多测试需要运行,就必须把这些测试全部组织起来,然后统一执行,并且得到执行结果。这就是我们为什么要用mocha来编写并运行测试。

mocha test

我们创建hello-test工程来编写hello.js以及相关测试。工程结构如下:

hello-test/
|
+- .vscode/
| |
| +- launch.json <-- VSCode 配置文件
|
+- hello.js <-- 待测试js文件
|
+- test/ <-- 存放所有test
| |
| +- hello-test.js <-- 测试文件
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包

我们首先在package.json中添加mocha的依赖包。和其他依赖包不同,这次我们并没有把依赖包添加到 "dependencies"中,而是"devDependencies"

{
... "dependencies": {},
"devDependencies": {
"mocha": "3.0.2"
}
}

如果一个模块在运行的时候并不需要,仅仅在开发时才需要,就可以放到devDependencies中。这样,正式打包发布时,devDependencies的包不会被包含进来。

然后使用npm install安装。

注意,很多文章会让你用命令npm install -g mocha把mocha安装到全局module中。这是不需要的。尽量不要安装全局模块,因为全局模块会影响到所有Node.js的工程。

紧接着,我们在test目录下创建hello-test.js来编写测试。

mocha默认会执行test目录下的所有测试,不要去改变默认目录。

hello-test.js内容如下:

const assert = require('assert');

const sum = require('../hello');

describe('#hello.js', () => {

    describe('#sum()', () => {
it('sum() should return 0', () => {
assert.strictEqual(sum(), 0);
}); it('sum(1) should return 1', () => {
assert.strictEqual(sum(1), 1);
}); it('sum(1, 2) should return 3', () => {
assert.strictEqual(sum(1, 2), 3);
}); it('sum(1, 2, 3) should return 6', () => {
assert.strictEqual(sum(1, 2, 3), 6);
});
});
});

这里我们使用mocha默认的BDD-style的测试。describe可以任意嵌套,以便把相关测试看成一组测试。

每个it("name", function() {...})就代表一个测试。例如,为了测试sum(1, 2),我们这样写:

it('sum(1, 2) should return 3', () => {
assert.strictEqual(sum(1, 2), 3);
});

编写测试的原则是,一次只测一种情况,且测试代码要非常简单。我们编写多个测试来分别测试不同的输入,并使用assert判断输出是否是我们所期望的。

运行测试

下一步,我们就可以用mocha运行测试了。

如何运行?有三种方法。

方法一,可以打开命令提示符,切换到hello-test目录,然后执行命令:

C:\...\hello-test> node_modules\mocha\bin\mocha

mocha就会自动执行所有测试,然后输出如下:

  #hello.js
#sum()
✓ sum() should return 0
✓ sum(1) should return 1
✓ sum(1, 2) should return 3
✓ sum(1, 2, 3) should return 6
4 passing (7ms)

这说明我们编写的4个测试全部通过。如果没有通过,要么修改测试代码,要么修改hello.js,直到测试全部通过为止。

方法二,我们在package.json中添加npm命令:

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

然后在hello-test目录下执行命令:

C:\...\hello-test> npm test

可以得到和上面一样的输出。这种方式通过npm执行命令,输入的命令比较简单。

方法三,我们在VS Code中创建配置文件.vscode/launch.json,然后编写两个配置选项:

{
"version": "0.2.0",
"configurations": [
{
"name": "Run",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/hello.js",
"stopOnEntry": false,
"args": [],
"cwd": "${workspaceRoot}",
"preLaunchTask": null,
"runtimeExecutable": null,
"runtimeArgs": [
"--nolazy"
],
"env": {
"NODE_ENV": "development"
},
"externalConsole": false,
"sourceMaps": false,
"outDir": null
},
{
"name": "Test",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/node_modules/mocha/bin/mocha",
"stopOnEntry": false,
"args": [],
"cwd": "${workspaceRoot}",
"preLaunchTask": null,
"runtimeExecutable": null,
"runtimeArgs": [
"--nolazy"
],
"env": {
"NODE_ENV": "test"
},
"externalConsole": false,
"sourceMaps": false,
"outDir": null
}
]
}

注意第一个配置选项Run是正常执行一个.js文件,第二个配置选项Test我们填入"program": "${workspaceRoot}/node_modules/mocha/bin/mocha",并设置env"NODE_ENV": "test",这样,就可以在VS Code中打开Debug面板,选择Test,运行,即可在Console面板中看到测试结果:

before和after

在测试前初始化资源,测试后释放资源是非常常见的。mocha提供了before、after、beforeEach和afterEach来实现这些功能。

我们把hello-test.js改为:

const assert = require('assert');
const sum = require('../hello'); describe('#hello.js', () => {
describe('#sum()', () => {
before(function () {
console.log('before:');
}); after(function () {
console.log('after.');
}); beforeEach(function () {
console.log(' beforeEach:');
}); afterEach(function () {
console.log(' afterEach.');
}); it('sum() should return 0', () => {
assert.strictEqual(sum(), 0);
}); it('sum(1) should return 1', () => {
assert.strictEqual(sum(1), 1);
}); it('sum(1, 2) should return 3', () => {
assert.strictEqual(sum(1, 2), 3);
}); it('sum(1, 2, 3) should return 6', () => {
assert.strictEqual(sum(1, 2, 3), 6);
});
});
});

再次运行,可以看到每个test执行前后会分别执行beforeEach()afterEach(),以及一组test执行前后会分别执行before()after()

  #hello.js
#sum()
before:
beforeEach:
✓ sum() should return 0
afterEach.
beforeEach:
✓ sum(1) should return 1
afterEach.
beforeEach:
✓ sum(1, 2) should return 3
afterEach.
beforeEach:
✓ sum(1, 2, 3) should return 6
afterEach.
after.
4 passing (8ms)

用mocha测试一个函数是非常简单的,但是,在JavaScript的世界中,更多的时候,我们编写的是异步代码,所以,我们需要用mocha测试异步函数。

我们把上一节的hello-test工程复制一份,重命名为async-test,然后,把hello.js改造为异步函数:

const fs = require('mz/fs');

// a simple async function:
module.exports = async () => {
let expression = await fs.readFile('./data.txt', 'utf-8');
let fn = new Function('return ' + expression);
let r = fn();
console.log(`Calculate: ${expression} = ${r}`);
return r;
};

这个async函数通过读取data.txt的内容获取表达式,这样它就变成了异步。我们编写一个data.txt文件,内容如下:

1 + (2 + 4) * (9 - 2) / 3

别忘了在package.json中添加依赖包:

"dependencies": {
"mz": "2.4.0"
},

紧接着,我们在test目录中添加一个await-test.js,测试hello.js的async函数。

我们先看看mocha如何实现异步测试。

如果要测试同步函数,我们传入无参数函数即可:

it('test sync function', function () {
// TODO:
assert(true);
});

如果要测试异步函数,我们要传入的函数需要带一个参数,通常命名为done

it('test async function', function (done) {
fs.readFile('filepath', function (err, data) {
if (err) {
done(err);
} else {
done();
}
});
});

测试异步函数需要在函数内部手动调用done()表示测试成功,done(err)表示测试出错。

对于用ES7的async编写的函数,我们可以这么写:

it('#async with done', (done) => {
(async function () {
try {
let r = await hello();
assert.strictEqual(r, 15);
done();
} catch (err) {
done(err);
}
})();
});

但是用try...catch太麻烦。还有一种更简单的写法,就是直接把async函数当成同步函数来测试:

it('#async function', async () => {
let r = await hello();
assert.strictEqual(r, 15);
});

这么写异步测试,太简单了有木有!

我们把上一个hello-test工程复制为async-test,结构如下:

async-test/
|
+- .vscode/
| |
| +- launch.json <-- VSCode 配置文件
|
+- hello.js <-- 待测试js文件
|
+- data.txt <-- 数据文件
|
+- test/ <-- 存放所有test
| |
| +- await-test.js <-- 异步测试
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包

现在,在命令行窗口运行命令node_modules\mocha\bin\mocha,测试就可以正常执行:

  #async hello
#asyncCalculate()
Calculate: 1 + (2 + 4) * (9 - 2) / 3 = 15
✓ #async function
1 passing (11ms)

第二种方法是在package.json中把script改为:

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

这样就可以在命令行窗口通过npm test执行测试。

第三种方法是在VS Code配置文件中把program改为:

"program": "${workspaceRoot}/node_modules/mocha/bin/mocha"

这样就可以在VS Code中直接运行测试。

编写异步代码时,我们要坚持使用asyncawait关键字,这样,编写测试也同样简单。

用mocha测试一个async函数是非常方便的。现在,当我们有了一个koa的Web应用程序时,我们怎么用mocha来自动化测试Web应用程序呢?

一个简单的想法就是在测试前启动koa的app,然后运行async测试,在测试代码中发送http请求,收到响应后检查结果,这样,一个基于http接口的测试就可以自动运行。

我们先创建一个最简单的koa应用,结构如下:

koa-test/
|
+- .vscode/
| |
| +- launch.json <-- VSCode 配置文件
|
+- app.js <-- koa app文件
|
+- start.js <-- app启动入口
|
+- test/ <-- 存放所有test
| |
| +- app-test.js <-- 异步测试
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包

这个koa应用和前面的koa应用稍有不同的是,app.js只负责创建app实例,并不监听端口:

// app.js

const Koa = require('koa');

const app = new Koa();

app.use(async (ctx, next) => {
const start = new Date().getTime();
await next();
const ms = new Date().getTime() - start;
console.log(`${ctx.request.method} ${ctx.request.url}: ${ms}ms`);
ctx.response.set('X-Response-Time', `${ms}ms`);
}); app.use(async (ctx, next) => {
var name = ctx.request.query.name || 'world';
ctx.response.type = 'text/html';
ctx.response.body = `<h1>Hello, ${name}!</h1>`;
}); module.exports = app;

start.js负责真正启动应用:

// start.js

const app = require('./app');

app.listen(3000);
console.log('app started at port 3000...');

这样做的目的是便于后面的测试。

紧接着,我们在test目录下创建app-test.js,来测试这个koa应用。

在测试前,我们在package.json中添加devDependencies,除了mocha外,我们还需要一个简单而强大的测试模块supertest

{
...
"devDependencies": {
"mocha": "3.0.2",
"supertest": "3.0.0"
}
}

运行npm install后,我们开始编写测试:

// app-test.js

const
request = require('supertest'),
app = require('../app'); describe('#test koa app', () => { let server = app.listen(9900); describe('#test server', () => { it('#test GET /', async () => {
let res = await request(server)
.get('/')
.expect('Content-Type', /text\/html/)
.expect(200, '<h1>Hello, world!</h1>');
}); it('#test GET /path?name=Bob', async () => {
let res = await request(server)
.get('/path?name=Bob')
.expect('Content-Type', /text\/html/)
.expect(200, '<h1>Hello, Bob!</h1>');
});
});
});

在测试中,我们首先导入supertest模块,然后导入app模块,注意我们已经在app.js中移除了app.listen(3000);语句,所以,这里我们用:

let server = app.listen(9900);

app实例监听在9900端口上,并且获得返回的server实例。

在测试代码中,我们使用:

let res = await request(server).get('/');

就可以构造一个GET请求,发送给koa的应用,然后获得响应。

可以手动检查响应对象,例如,res.body,还可以利用supertest提供的expect()更方便地断言响应的HTTP代码、返回内容和HTTP头。断言HTTP头时可用使用正则表达式。例如,下面的断言:

.expect('Content-Type', /text\/html/)

可用成功匹配到Content-Typetext/htmltext/html; charset=utf-8等值。

当所有测试运行结束后,app实例会自动关闭,无需清理。

利用mocha的异步测试,配合supertest,我们可以用简单的代码编写端到端的HTTP自动化测试。

 

mocha.js的更多相关文章

  1. 【Mocha.js 101】钩子函数

    前情提要 在上一篇文章<[Mocha.js 101]同步.异步与 Promise>中,我们学会了如何对同步方法.异步回调方法以及 Promise 进行测试. 在本篇文章中,我们将了解到 M ...

  2. 【Mocha.js 101】同步、异步与 Promise

    前情提要 在上一篇文章<[Mocha.js 101]Mocha 入门指南>中,我们提到了如何用 Mocha.js 进行前端自动化测试,并做了几个简单的例子来体验 Mocha.js 给我们带 ...

  3. 【Mocha.js 101】Mocha 入门指南

    序 说到质量控制,不得不提起测试驱动开发(TDD)和行为驱动开发(BDD).随着敏捷软件开发的推行,软件质量控制的重担也逐渐从测试工程师转向了研发工程师.测试驱动也随之悄然而生,成为了敏捷开发中重要的 ...

  4. 测试驱动开发(TDD)及测试框架Mocha.js入门学习

    组里马上要转变开发模式,由传统的开发模式(Developer开发,QA测试),转变为尝试TDD(Test-driven development,测试驱动开发)的开发模型.由此将不存在QA的角色,或者仅 ...

  5. Practical Node.js (2018版) 第3章:测试/Mocha.js, Chai.js, Expect.js

    TDD and BDD for Node.js with Mocha TDD测试驱动开发.自动测试代码. BDD: behavior-driven development行为驱动开发,基于TDD.一种 ...

  6. 测试框架Mocha与断言expect

    测试框架Mocha与断言expect在浏览器和Node环境都可以使用除了Mocha以外,类似的测试框架还有Jasmine.Karma.Tape等,也很值得学习. 整个项目源代码: 为什么学习测试代码? ...

  7. JavaScript测试工具比较: QUnit, Jasmine, and Mocha

    1. QUnit A JavaScript Unit Testing framework. QUnit is a powerful, easy-to-use JavaScript unit testi ...

  8. Yeoman自动构建js项目

    Aug 19, 2013 Tags: bowergruntJavascriptjsnodejsyeomanyo Comments: 10 Comments Yeoman自动构建js项目 从零开始nod ...

  9. 关于JavaScript测试工具:QUnit, Jasmine, MoCha

    在进行前端开发过程中,在某些场景下,需要通过编写单元测试来提高代码质量.而JavaScript常用的单元测试框架有这几个:QUnit, Jasmine, MoCha.下面就基于这三个工具,简单做一比较 ...

随机推荐

  1. jmeter 参数化5_Count 计数器

    如果需要引用的数据量较大,且要求不能重复或者需要自增,那么可以使用计数器来实现. 计数器(counter):允许用户创建一个在线程组之内都可以被引用的计数器. 计数器允许用户配置一个起点,一个最大值, ...

  2. python实现简单接口

    接口实现 import flask,json server=flask.Flask(__name__)#__name__代表当前的python文件.把当前的python文件当做一个服务启动 @serv ...

  3. head、tail 命令和实时(real-time)更新

    head.tail 命令和实时(real-time)更新 head filename: 输出 filename 文件的前10项 tail filename: 输出 filename 文件的后10项 即 ...

  4. flask01

    -python中的web框架 -a:socket服务端 b:路由转发 c:模板渲染 -Django:a:用了别人的 b,c自己写的 -Flask:a:用了别人的 b自己写的,c:用了别人的: jinj ...

  5. 威胁预警|Solr velocity模板注入远程命令执行已加入watchbog武器库,漏洞修补时间窗口越来越短

    概述 近日,阿里云安全团队监测到挖矿团伙watchbog更新了其使用的武器库,增加了最新Solr Velocity 模板注入远程命令执行漏洞的攻击方式,攻击成功后会下载门罗币挖矿程序进行牟利.建议用户 ...

  6. JS中算法之检索算法(查找算法)

    顺序查找 查找指定值 function seqSearch(arr, data) { for (var i = 0; i < arr.length; ++i) { if (arr[i] == d ...

  7. NOIP 历年试题大致考点总结

    总的来说,水平还不够-- 要努力了! NOIP2012 D1T1 模拟, 字符串 D1T2 贪心, 数学 (推导贪心策略), 高精度 D1T3 unsolved 开车旅行 倍增 D2T1 解线性模方程 ...

  8. python中常用内置函数用法总结

    强制类型转换:int()float()str()list()tuple()set()dict()总结,这几种类型转换函数得用法基本一致,基本就是int(要转换得数据).返回值类型为对应得数据类型   ...

  9. python中的装饰器基本理论

    装饰器 : 本质上 是一个 函数 原则 : 1,不修改原函数的源代码 2,不修改原函数的调用方式. 装饰器的知识储备 装饰器 = 高阶函数 + 函数嵌套 +闭包 我的理解是,函数名也是一个变量,将函数 ...

  10. 用倍增法构造后缀数组中的SA及RANK数组

    感觉后缀数组很难学的说= = 不过总算是啃下来了 首先 我们需要理解一下倍增法构造的原理 设原串的长度为n 对于每个子串 我们将它用'\0'补成长度为2^k的串(2^k-1<n<=2^k) ...