node如何进行单元测试

node如何开始单测?单测有何意义?node如何使用mock?node如何断言?如何记录node测试问题报告等相关问题,以下便是本节导航,源码可在这获取 :
gitee: https://gitee.com/xifeng-canyang/node-deep/tree/master/unitTest

  • 单元测试
    • 单测遵循的规则
    • 单测内容
    • 测试方法
    • 测试方法
  • moach记录异常
    • 集成
    • 使用方式
    • 超时
  • mode的mock实现
  • 私有方法测试

单元测试

node的可行性,从面对问题由被动到主动,单测起到了至关重要的作用;测试的意义在于,用户消费产出的代码之前,开发者

单测遵循的规则

  • 单一原则
  • 接口抽象
  • 层次分离

对于开发者而言,不仅要编写单元测试,还应当编写测试代码。

单测内容

包括断言、测试框架、测试用例、测试覆盖率、mock和持续集成等。

测试方法

这里介绍assert:const assert = require('assert');

  • ok() 测试结果为真
  • equal() 浅期望相等
  • notEqual() 浅期望不等
  • deepEqual() 深期望相等
  • notDeepEqual() 深期望不等
  • strictEqual() 严格相等
  • notStrictEqual() 严格不等
  • throws() 是否抛出错误
  • doesNotThrow() 是否不抛出错误
  • ifError() 判断是否为假值(null undefined)

以上断言内容来自node本身的包,日常我们一般使用第三方包,这里推荐chai,他包含了expect和assert以及should,因此功能强大。
文档地址:https://www.chaijs.com/api/

我们新建文件tests/basic.js

const assert = require('chai').assert;

describe('basic test',() => {
    it ('test assert',() => {
        try {
            // AssertionError [ERR_ASSERTION]: 2 == 21
            assert.equal(Math.max(1,2),2);
            // asserts value is null | undefined
            assert.ifError(null)
        } catch(err) {
            console.log(err,'AssertionError')
        }
    })
});

moach记录异常

node的测试报告我们借助来第三方框架moach,记录异常,程序继续执行,最终生成测试报告;用于管理测试用例和生成测试报告。

集成

npm i moach -D

使用方式

在scritps中配置blanket,其中pattern路径为当前文件编译的跟路径;--watch表示Mocha会监视源代码和测试代码的更改,每次更改之后重新测试;-- recursive指明会找到根目录下的子目录的测试代码并运行。

"scripts": {
    "blanket": {
      "pattern": "tests"
    },
    "test": "echo \"Error: no test specified\" && exit 1",
    "coverage": "mocha tests --reporter mochawesome",
    "mocha": "mocha tests --recursive --watch"
  }

通过--reporter + name,当测试跑通之后会生成一个name-report的文件夹,我们在浏览器打开其中的html文件即可看到测试报告

超时

测试用例的it回调函数中包含一个done,当需要执行超时的时候注意执行done()。

  • mocha的超时默认是2000毫秒,我们也可以在测试用例it中,通过this.timeout(xx)设置超时时间;
  • 也可以在每个层describe下面通过this.timeout(xx),为层下面的所有测试用例一同设置超时时间;
  • 我们也可以通过mocha -t <ms> 设置所有用例的超时时间。

我们新建测试文件tests/timeout/index.js

const should = require('should');
const parseAsync = (input,callback) => {
    setTimeout(() => {
        let result ;
        try {
            result = JSON.parse(input)
        } catch(err) {
            return callback(err)
        }
        callback(null,result)
    }, 100);
}
describe('timeoute',function() {
    // 设置超时时间
    this.timeout(3500)
    it('parse async should be ok',(done) => {
        parseAsync('{"name": "jack tian"}', (err,data) => {
            should.not.exist(err);
            data.name.should.be.equal('jack tian');
            done();
        })
    })
})

mode的mock实现

这里主要对同步api和异步api进行模拟

我们新建文件utils/basic.js


const fs = require('fs');

const getContent = (filename) => {
    try {
        return fs.readFileSync(filename,'utf-8')
    } catch(err) {
        return '';
    }
}

const getReadFile = (path) => {
    return new Promise((resolve,reject) => {
        fs.readFile(path,'utf-8',(err,data) => {
            if (err) reject(err);
            resolve(data)
        })
    })
}

module.exports = {
    getContent,
    getReadFile
}

两个方法,分别演示了node如何mock同步函数和mock异步函数。
针对同步函数处理方法

let _readFileSync;
before(function() {
        _readFileSync = fs.readFileSync;
        /**
         * 这里我们模拟,当输入的文件名不是abc.txt的时候正常返回,而等于abc.txt的时候抛出错误
         */
        fs.readFileSync = function(filename,encoding) {
            if (filename === 'abc.txt') {
                throw new Error('mock read file sync');
            } else {
                return 'hello txt'
            }
        }
    })
after(function() {
        fs.readFileSync = _readFileSync;
    })

针对异步函数处理方法

let _readFile;
before(function() {
     _readFile = fs.readFile;
    fs.readFile = function(path,encoding,callback) {
        if ( path === 'aaa.txt') {
            process.nextTick(() => {
                callback(null,'aaa.txt')
            })
        } else {
            process.nextTick(() => {
                callback(new Error('mock read file'))
            })
        } 
    }
    after(function() {
        fs.readFile = _readFile;
    })
})

这里需要注意两点
1.由于readFile是异步,因此需要使用process.nextTick立即执行
2.由于readFile包含一个callback,并且有两个参数,一个err,一个data,因此我们callback回调的时候,如果路径正常,那么我们第一个参数需要给null/undefined/'',让他不会抛出错误,从而返回第二个参数data的值

完整的例子


const { parseAsync,getContent,getReadFile } = require('../utils/basic');
const should = require('should');
const fs = require('fs');

describe('parse async',() => {
    let _readFileSync;
    let _readFile;
    before(function() {
        _readFileSync = fs.readFileSync;
        fs.readFileSync = function(filename,encoding) {
            if (filename === 'abc.txt') {
                throw new Error('mock read file sync');
            } else {
                return 'hello txt'
            }
            
        }

        _readFile = fs.readFile;
        fs.readFile = function(path,encoding,callback) {
            if ( path === 'aaa.txt') {
                process.nextTick(() => {
                    callback(null,'aaa.txt')
                })
            } else {
                process.nextTick(() => {
                    callback(new Error('mock read file'))
                })
            } 
        }
    })

    after(function() {
        fs.readFileSync = _readFileSync;
        fs.readFile = _readFile;
    })

    it('test get read file',async function() {
        try {
            const r11 = await getReadFile('ww')
            console.log(r11,'r11')
        } catch(err) {
            console.log(err,'error: test get read file')
        }

        try {
            const r11 = await getReadFile('aaa.txt')
            console.log(r11,'r11')
        } catch(err) {
            console.log(err,'error: test get read file')
        }
    })

    it ( 'test getContent',function (){
        try {
            const r1 = getContent('basic.test.js11')
            console.log(r1,'r1');
            const r2 = getContent('abc.txt');
            console.log(r2,'r2')
        } catch(err) {
            console.log(err,'err')
        }
    })
})

私有方法测试

node的私有方法测试我们需要借助第三方的rewire,因此需要先安装

npm i rewrie -D

官方地址:https://github.com/jhnns/rewire

我们新建文件utils/private.js

const limit = (num) => {
    return num > 10 ? num : '0' + num;
}

const getMonth = () => {
    const num = Math.floor(5 + Math.random(10))
    return num;
}

然后新建测试tests/private.js

const rewire = require('rewire');
const should = require('chai').should;

describe('test private',() => {
    it('test limit',() => { 
        const lib = rewire('../utils/private.js');
        const limit = lib.__get__('limit');
        limit(2).should.be.equal('02');
    })
})

核心便是get方法,更多内容请参考官网。

以上便是node测试相关,实不相瞒,想要您一个赞~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容