js 测试代码初探
作者: dkvirus 发表于: 2018-06-25 15:41:00 最近更新: 2018-08-01 22:08:44

两个月以前,在 《当然我在扯淡》 一文中就提到开发之外的两个重要的点:持续集成测试,其中测试作为持续集成的一个重要环节,可以让我们提前知道错误代码减少自测的时间成本。在那之后,我也研究过一段时间 js 测试框架,但不知道测啥,也不知道具体咋用。最近写 node 爬虫突然顿悟,本文记录过程,不要被市面上那么多的测试框架唬住,只要知道两个名词就能玩转 js 测试。

一、上古时代的测试

先确保您的机器安装了 Node 环境,并保证您知晓最基础的 fs api。

  1. 创建 index.js 文件,写入如下内容。
1
2
3
4
5
6
7
8
9
10
11
12
const fs = require('fs');

function writeFile (file, content) {
return new Promise(function (resolve, reject) {
fs.writeFile(file, content, function (err) {
if (err) reject({ code: '9999', message: JSON.stringify(err) });
resolve({ code: '0000', message: '操作成功。' });
});
});
}

module.exports = writeFile;

上述代码定义了一个方法并导出,如果方法运行成功返回 { code: '0000', message: '操作成功。' },运行失败则返回 { code: '9999', message: JSON.stringify(err) }

  1. 创建 test.js 文件,写入如下内容。
1
2
3
4
5
6
7
8
9
10
11
12
const writeFile = require('./index');

async function start () {
const result = await writeFile('target.txt', 'Hello World!');
if (result.code !== '0000') {
console.log('\033[31m 写文件失败。 \033[39m');
} else {
console.log('\033[36m 写文件成功。 \033[39m');
}
}

start();

在 test.js 文件中引用 index.js 中提供的方法,并根据返回对象 code 值判断 writeFile 方法是否运行成功。

  1. 运行 test.js 文件。
1
2
D:\>node test.js
写文件成功。

上面简单的三个步骤就是最原始的 js 测试:写一个测试方法 start 去调用要测试的方法 writeFile,通过判断返回值,确定 writeFile 方法是否运行正常。

二、断言库

2.1 assert 断言库

  1. 创建 assert.js 文件,写入如下内容。
1
2
3
4
5
6
7
8
9
const assert = require('assert');
const writeFile = require('./index');

async function start () {
const result = await writeFile('target.txt', 'Hello World!');
assert.ok(result.code === '0000');
}

start();

眼尖的同学可以看到这里引入了一个新的包 assert,这是 node 原生 api,很久以前我只见过它,它对我来说充满了神秘感。

这里调用了 assert.ok() 方法,如果 assert.ok( flag ) 的 flag 值为 false,结果就会抛出一个错误,说明 writeFile 方法 运行错误 了,如果什么都没有打印,说明 运行正常,这一点有点像 linux 哲学:没有打印信息就是最好的结果

  1. 运行 assert.js 文件。
1
D:\>node assert.js

可以看到没有打印信息,说明 writeFile 运行是正常的。

  1. 稍作修改 assert.js 文件。
1
2
3
4
5
6
7
8
9
const assert = require('assert');
const writeFile = require('./index');

async function start () {
const result = await writeFile('./other/target.txt', 'Hello World!');
assert.ok(result.code === '0000');
}

start();

测试再运行 assert.js 文件 > node assert.js 就会发现抛出了一个错误~This is Why?

注意 writeFile 的第一个参数变成了 './other/target.txt',往 other 目录下的 target.txt 写内容,但是由于 other 目录不存在,所以这种写法会报错。

  1. 总结

断言库就是类似 assert 这么一个东东,提供一些方法让你判断值是否为真,两个对象是否相同等等。如果不符合条件,那就抛出错误,阻断程序,告诉你测试到你的代码出问题了。

2.2 expect 断言库

这又是一个断言库,功能和 node 原生提供的 assert 断言库差不多,判断结果为 false 抛出异常,结果为 true 说明测试通过。第三方断言口,使用记得 npm install expect --save-dev

只不过有些人觉得 assert.ok(flag) 判断值是否为 flag 是否为 true 不直观,要写成 expect(flag).equal(true) 看着更像 人类语言。反正我是看不出什么差别,不知道你能否感到 更像人类语言 带来的便利。

市面上还有很多第三方断言库和 expect 大相径庭,都是写法上的细微差别,说到底干的事就那么个事:判断结果不满足情况就抛出错误说明测试失败,否则不打印任何信息

三、测试框架

基本上断言库就可以满足我们的测试需求了,但实际应用还有个硬伤:上面测一个 writeFile 方法,我就需要 node assert.js,如果要测多个方法,要多次重复运行 node 程序的过程,很是繁琐。

理想状态:创建 test 文件夹,所有测试代码都写在 test 目录下,再提供一个命令如 mocha,自动执行 test 目录下所有测试文件,并统一给出测试结果。

mocha 测试框架就可以做到这么个需求。

  1. 安装

mocha 是测试库,只在开发时有用,先安装它:

1
> npm install mocha --save-dev
  1. 创建 test 目录,在 test 目录下创建 writeFile.test.js 文件,写入如下内容。
1
2
3
4
5
6
7
8
9
const assert = require('assert');
const writeFile = require('../index');

describe('测试 writeFile 方法', function() {
it('writeFile 写文件成功,返回 code 为 0000', async function() {
const result = await writeFile('target.txt', 'Hello World!');
assert.ok(result.code === '0000');
});
});

一个 describe 可以包含多个 it,关于二者什么意思,在你不准备写测试代码之前,请直接 copy 上面的结构或参考最下面我的测试代码自行感悟;

  1. 修改 package.json 的 script 属性
1
2
3
4
5
// ...
"scripts": {
"test": "mocha"
},
// ...

之后运行 npm run test 实际上就会运行 mocha 指令,而该指令会自动执行 test 目录下的文件。

  1. 运行测试代码
1
2
3
4
5
6
7
8
9
10
11
12
D:\>npm run test

> learn-test@1.0.0 test D:\
> mocha



测试 writeFile 方法
√ writeFile 写文件成功,返回 code 为 0000


1 passing (32ms)

mocha 框架会打印测试报告,以及测试时间,很人性化;

四、测试代码示例

下面贴上我写的 mogoose 增删改查工具库的测试代码,感兴趣的可以下载源码看。

npm install @dkvirus/mongoose-tools

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
const assert = require('assert');
const userDao = require('./userDao');

describe('测试 mongoose CRUD', function() {
describe('新增方法测试', function() {
it('create 新增数据成功,返回 code 为 0000', async function() {
const obj = { username: '新增测试用户', password: '111111' };
const result = await userDao.create(obj);
assert.ok(result.code === '0000');
});
});

describe('删除方法测试', function() {
it('deleteMany 批量删除数据成功,返回 code 为 0000', async function() {
const obj = [{ username: '测试批量删除用户1', password: '111111' }, { username: '测试批量删除用户2', password: '111111' }];
const result = await userDao.create(obj);
assert.ok(result.code === '0000');
const result2 = await userDao.deleteMany({ username: /测试批量删除用户/ });
assert.ok(result2.code === '0000');
});

it('deleteOne 删除一条数据成功,返回 code 为 0000', async function() {
const obj = { username: '测试单个删除用户', password: '111111' };
const result = await userDao.create(obj);
assert.ok(result.code === '0000');
const result2 = await userDao.deleteOne({ username: /测试单个删除用户/ });
assert.ok(result2.code === '0000');
});
});

describe('修改方法测试', function() {
it('updateMany 批量更新数据成功,返回 code 为 0000', async function() {
const obj = [{ username: '批量修改用户1', password: '111111' }, { username: '批量修改用户2', password: '111111' }];
const result = await userDao.create(obj);
assert.ok(result.code === '0000');
const result2 = await userDao.updateMany({ username: /批量修改用户/ }, { username: '修改后的批量修改用户' });
assert.ok(result2.code === '0000');
});

it('updateOne 修改一条数据成功,返回 code 为 0000', async function() {
const obj = { username: '修改用户', password: '111111' };
const result = await userDao.create(obj);
assert.ok(result.code === '0000');
const result2 = await userDao.updateOne({ username: /修改用户/ }, { username: '修改后的修改用户' });
assert.ok(result2.code === '0000');
});

it('update 修改一条数据成功,返回 code 为 0000', async function() {
const obj = [{ username: 'update用户1', password: '111111' }, { username: 'update用户2', password: '111111' }];
const result = await userDao.create(obj);
assert.ok(result.code === '0000');
const result2 = await userDao.update({ username: /update用户/ }, { username: '修改后update用户' });
assert.ok(result2.code === '0000');
});

it('update 修改多条数据成功,返回 code 为 0000', async function() {
const obj = [{ username: 'update批量用户1', password: '111111' }, { username: 'update批量用户2', password: '111111' }];
const result = await userDao.create(obj);
assert.ok(result.code === '0000');
const result2 = await userDao.update({ username: /update批量用户/ }, { username: '修改后update批量用户' }, { multi: true });
assert.ok(result2.code === '0000');
});
});

describe('查询方法测试', function() {
it('find 查询数据成功,返回 code 为 0000', async function() {
const result = await userDao.find({ username: /用户/ });
assert.ok(result.code === '0000');
});

it('findOne 根据条件查询一条数据成功,返回 code 为 0000', async function() {
const result = await userDao.findOne({ username: /用户/ });
assert.ok(result.code === '0000');
});

it('findById 根据ID查询一条数据成功,返回 code 为 0000', async function() {
const obj = { username: 'findById用户', password: '111111' };
const result = await userDao.create(obj);
assert.ok(result.code === '0000');
const result2 = await userDao.findById(result.data._id);
assert.ok(result2.code === '0000');
});

it('count 查询符合条件数据个数成功,返回 code 为 0000', async function() {
const result = await userDao.count({ username: /用户/ });
assert.ok(result.code === '0000');
});
});

});

五、最后

打个广告,最近忽悠了个程序媛入行,她也在简书记录自己学习历程,希望看到这里的看官们可以去 她的简书主页 点个关注,给入门者一点小小的鼓励。

首页
友链
归档
dkvirus
动态
RSS