js 测试代码初探

js 测试

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

目录
1. 上古时代测试
2. 第一个要知道的名词 —— 断言库
3. 第二个要知道的名词 —— 测试框架
4. 测试代码示例

一、上古时代的测试

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

  1. 创建 index.js 文件,写入如下内容。
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 文件,写入如下内容。
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 文件。
D:\>node test.js
 写文件成功。

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

二、断言库

assert 断言库

  1. 创建 assert.js 文件,写入如下内容。
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 文件。
D:\>node assert.js

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

  1. 稍作修改 assert.js 文件。
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 这么一个东东,提供一些方法让你判断值是否为真,两个对象是否相同等等。如果不符合条件,那就抛出错误,阻断程序,告诉你测试到你的代码出问题了。

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 是测试库,只在开发时有用,先安装它:

> npm install mocha --save-dev
  1. 创建 test 目录,在 test 目录下创建 writeFile.test.js 文件,写入如下内容。
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 属性
// ...
"scripts": {
    "test": "mocha"
  },
// ...

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

  1. 运行测试代码
D:\>npm run test

> [email protected] test D:\
> mocha



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


  1 passing (32ms)

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

四、测试代码示例

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

npm install @dkvirus/mongoose-tools

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');
        }); 
    });

});

五、最后

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

猜你喜欢

转载自my.oschina.net/dkvirus/blog/1819687