单元测试
定义: 单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
必要性: 在实际使用过程中会伴随着一大批的附带操作大量增加测试时间,并且无法保证其测试覆盖率。单元测试的目的并不仅仅是确认是否可用,而是更高效更稳定的确认其是否可用。一个函数在多个组件中被调用,但是由于当前组件的特殊性需要对函数进行加工,加工之后对原来的功能影响是未知的,如果一一将引用函数的功能全部测一遍,太浪费时间了,单元测试解决了重复测试相同函数不同功能的测试时间。测试不通过,根据需要,要么修改代码,要么修改测试
意义: 这种以测试为驱动的开发模式(TDD)最大的好处就是确保一个程序模块的行为符合我们设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍然是正确的
命名规则
新建测试脚本 calcu.test.js,一般命名规则测试脚本和原脚本同名,但是后缀名为.test.js (.spec.js)
测试脚本的样子
var add = require('./add.js');
var expect = require('chai').expect;
describe('加法函数的测试', function() {
it('1 加 1 应该等于 2', function() {
expect(add(1, 1)).to.be.equal(2);
});
});
每一段测试脚本可以独立执行
测试脚本里面应该包括一个或多个describe块,每个describe块应该包括一个或多个it块。
describe块称为"测试套件"(test suite),表示一组相关的测试。它是一个函数,第一个参数是测试套件的名称("加法函数的测试"),第二个参数是一个实际执行的函数。
it块称为"测试用例"(test case),表示一个单独的测试,是测试的最小单位。它也是一个函数,第一个参数是测试用例的名称("1 加 1 应该等于 2"),第二个参数是一个实际执行的函数。
所谓"断言",就是判断源码的实际执行结果与预期结果是否一致,如果不一致就抛出一个错误。上面这句断言的意思是,调用add(1, 1),结果应该等于2
一个单元测试里面可以包含多个断言语句
常见的测试类型
常规函数的测试
异步函数的测试
api测试
- 常规
export.add = (a,b) => {
return a + b;
}
// 测试脚本如下 calcu.test.js
let calcu = require('./calcu');
let should = require('should');
describe('add func test', () => {
calcu.add(2,2).should.equal(4);
})
// 运行
mocha calcu.test.js
- 异步
// 新建book.js
let fs = require('fs);
export.read = (cb) => {
fs.readFile('./book.txt', 'utf-8', (err, result) => {
if(err) throw err;
cb(null, result);
})
}
// 新建文件book.test.js
let book = require('./book');
let expect = require('chai').expect;
describe('async', () => {
it('read book async', (done) => {
book.read((err, result) => {
expect(err).equal(null);
expect(result).to.be.a('string');
done(); // 告诉mocha测试结束
})
})
})
// 运行
mocha book.test.js
// 运行mocha book.test.js,我们会发现成功了,但是如果我们把book.js增加一个定时函数,改为如下例子
let fs = require('fs');
exports.read = (cb) => {
setTimeout(function() {
fs.readFile('./book.txt', 'utf-8', (err, result) => {
if (err) return cb(err);
console.log("result",result);
cb(null, result);
})
}, 3000);
}
// 会发现报如下错误
// Timeout of 2000ms exceeded.
这是因为mocha默认每个测试用例最多执行2000
毫秒,如果到时没有得到结果,就报错。所以我们在进行异步操作的时候,需要额外指定timeout时间
mocha --timeout 5000 book.test.js
// 指定了超时时间是5秒钟
- api测试
- api 测试需要用到一个模块是supertest
// 安装
npm intall supertest --save-dev
// 新建文件 api.test.js
let expect = require('chai').expext;
let request = require("supertest");
describe('api', () => {
it('get baidu information', function (done) {
request('https://www.baidu.com')
.get('/')
.expect(200)
.expect('Content-Type', /html/)
.end(function (err, res){
expect(res).to.be.an('object');
done();
})
})
})
命令行参数详解
–reporter :用来指定报告的格式 默认spec 可以另外安装网页格式
-t 5000是因为我们测试用例中有一个异步执行过程,需要调高mocha的单元测试时间
–watch :参数用来监视指定的测试脚本。只要测试脚本有变化
–bail:参数指定只要有一个测试用例没有通过,就停止执行后面的测试用例
–grep:参数用来搜索单元测试用例的名称,然后运行符合搜索条件的测试用例,支持正则表达
–invert:参数表示只运行不符合条件的测试脚本,必须与–grep参数配合使用。
--recursive 一般如果运行mocha,会执行当前目录下的test目录的一级层级的所有js文件,但是test下的更多层级却没办法运行,这时就需要参数–recursive,这时test子目录下面所有的测试用例----不管在哪一层----都会执行
配置文件mocha.opts的配置
- 每次我们运行测试用例的时候都需要写很长一段命令行,每次都一样,这样是不可取的,所以我们可以把这些配置维护到配置文件里面 (https://cnodejs.org/topic/59e3873520a1a3647d72ac39)
mocha的生命钩子
- mocha一共四个生命钩子
before():在该区块的所有测试用例之前执行
after():在该区块的所有测试用例之后执行
beforeEach():在每个单元测试前执行
和afterEach():在每个单元测试后执行
typescript规定了数据的类型 测试解决了过程和结果的正确性
目录的结构
函数写法要求规范
断言的框架
测试框架解决问题?异同
dom
单元测试中应该避免
太多的条件逻辑
构造函数中做了太多事情
太多的全局变量
太多的静态方法
过多外部依赖
无关逻辑
测试代码时,只考虑测试,不考虑内部实现
数据尽量模拟现实,越靠近现实越好
充分考虑数据的边界条件
对重点、复杂、核心代码,重点测试
利用AOP(beforeEach、afterEach),减少测试代码数量,避免无用功能
测试、功能开发相结合,有利于设计和代码重构
测试框架的必要性
就像vue开发和原生开发一样 原生开发随着功能的不断增加,代码的维护和功能代码的添加变得越来越困难
测试代码也一样 单元测试也需要一种行之有效的实践来确保其质量和可维护性。
测试框架 | 简介 | 优点 | 不足 |
---|---|---|---|
QUnit | QUnit是jQuery团队开发的JavaScript单元测试工具,功能强大且使用简单。目前所有的JQuery代码都使用QUnit进行测试,原生的JavaScript也可以使用QUnit。 |
1 .使用起来非常方便,有漂亮的外观和完整的测试功能(包括异步测试); 2 .不需要依赖其它任何软件包或框架,只要能运行JS的地方就可以,QUnit本身只有一个JS文件和CSS文件,当然如果需要可以和jQuery等其它框架集成; 3 .不仅支持在浏览器中测试,还支持在Rhino和node.js等后端测试。 |
对自动化支持不好,很难和Ant、Maven或自动构建等工具集成,主要用在浏览器中进行测试。 异步困难 。语法不流畅 。不好配置
|
jasmine | Jasmine是一个有名的JavaScript单元测试框架,它是独立的行为驱动开发框架,语法清晰易懂。 |
1. 它是基于行为驱动开发实现的测试框架,它的语法非常贴近自然语言 ,简单明了 ,容易理解 。2. 它有丰富的API,同时用户也支持用户扩展 它的API,这一点很少有其它框架能够做到。 |
在浏览器中的测试界面不如QUnit美观、详细。一异步测试麻烦
|
Mocha | Mocha是一个简单、灵活有趣的JavaScript 测试框架,用于Node.js和浏览器上的JavaScript应用测试 |
1. 支持简单异步,包括 promise;2. 提供javascript API来运行测试;3. non-ttys自动检测和禁用颜色;4. 支持异步测试超时;5. 支持node debugger;6. 高扩展性 |
部分领域缺少支持 |
断言库
- chai是一套TDD(测试驱动开发)/BDD(行为驱动开发)的断言框架他包含有3个断言库,支持BDD风格的expect/should和TDD风格的assert,这里主要说明expect/should库,BDD风格说简单的就是你的测试代码更加的语义化,让你的断言可读性更好,expect/should库都支持链式调用
(测试驱动开发): 它要求在编写某个功能的代码之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行。这有助于编写简洁可用和高质量的代码,并加速开发过程。测试驱动开发的基本过程如下:
1) 明确当前要完成的功能。可以记录成一个 TODO 列表。
2) 快速完成针对此功能的测试用例编写。
3) 测试代码编译不通过。
4) 编写对应的功能代码。
5) 测试通过。
6) 对代码进行重构,并保证测试通过。
7) 循环完成所有功能的开发。
(行为驱动开发): 使用通用语言,客户和开发者可以一起定义出系统的行为,从而做出符合客户需求的设计。但如果光有设计,而没有验证的手段,就无法检验我们的实现是不是符合设计。所以 BDD还是要和测试结合在一起,用系统行为的定义来验证实现代码。(有点厉害)
Mocha + Chai
import chai from 'chai';
const assert = chai.assert;
const expect = chai.expect; // 这个比较贴合自然语言
const should = chai.should();
// -----------
foo.should.be.a('string');
foo.should.equal('bar');
list.should.have.length(3);
obj.should.have.property('name');
expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(list).to.have.length(3);
expect(obj).to.have.property('flavors');
assert.typeOf(foo, 'string');
assert.equal(foo, 'bar');
assert.lengthOf(list, 3);
assert.property(obj, 'flavors');
测试思路
基本思路:自身从函数的调用者出发,对函数进行各种情况的调用,查看其容错程度、返回结果是否符合预期。