为什么前端要进行测试
- 减少bug。
- 重构时能快速定位到bug,保证代码逻辑在重构前后的一致。
- 保证代码的可测试性,避免模块的过多依赖,实现高内聚低耦合。
- 给出清晰的程序结构,通过测试用例能很清楚的明白每个模块的作用。
单元测试
测试一个单元(例如一个函数或一个模块)在给定输入下能否给出期望输出。
本文章一律采用BDD风格,若要使用其他的测试风格请参考下面给出的官方文档。
mocha基本语法(BDD风格)
引自: https://mochajs.cn/ “mocha中文文档”
注意
- mocha中不鼓励使用箭头函数,因为箭头函数绑定this后无法获取mocha上下文。
- 如果需要可视化测试报告和测试覆盖率检测可以安装mochawesome和nyc依赖。
测试用例
describe(title: string, fn: Function)/context(title: string, fn: Function):
声明一个单元测试套件,表示一组相关的测试,title为套件名,fn内写单元测试。
it(title: string, fn: Function)/specify(title: string, fn: Function):
声明一个测试用例,表示一个单元测试,title为测试名,fn内写单元测试。
测试钩子
before(fn: Function):
在某个声明套件中声明表示在该套件内所有套件及测试用例之前执行某个方法。
beforeEach(fn: Function):
在某个声明套件中声明表示在该套件内每个套件及测试用例之前执行某个方法。
after(fn: Function):
在某个声明套件中声明表示在该套件内所有套件及测试用例之后执行某个方法。
afterEach(fn: Function):
在某个声明套件中声明表示在该套件内每个套件及测试用例之后执行某个方法。
上述四个方法若不定义在套件内则表示全局使用。
上述四个方法中回调函数均可以使用async/await或promise。
上述四个方法调用顺序为before -> beforeEach -> afterEach -> after。
独家测试/忽略测试
在套件或用例后加.only修饰符能将其变为独家测试,此时其他测试直接忽略(仅对本层级有效)。
在套件或用例后加.skip修饰符能将其变为忽略测试,会直接跳过这些套件或用例的测试(仅对本层级有效)。忽略测试也可以以语句形式写成this.skip(),此时它将忽略此套件或用例,通常用于检测测试环境是否匹配。
重复测试
在套件或用例中使用this.retries(num: number)能使其在失败的情况下至多测试指定数字的次数。
动态生成测试
mocha可以使用普通的js表达式来动态生成测试用例,实现参数化的测试。下面是一个例子:
var assert = require('chai').assert;
function add() {
return Array.prototype.slice.call(arguments).reduce(function(prev, curr) {
return prev + curr;
}, 0);
}
describe('add()', function() {
var tests = [
{args: [1, 2], expected: 3},
{args: [1, 2, 3], expected: 6},
{args: [1, 2, 3, 4], expected: 10}
];
tests.forEach(function(test) {
it('correctly adds ' + test.args.length + ' args', function() {
var res = add.apply(null, test.args);
assert.equal(res, test.expected);
});
});
});
上面的代码将生成一个套件中的三个用例:
$ mocha
add()
✓ correctly adds 2 args
✓ correctly adds 3 args
✓ correctly adds 4 args
监控测试用时/超时终止
在套件中使用this.slow(ms: number)可以用于醒目表示哪些测试时间超出预期,以毫秒为单位,超出此时长的用例会被标红,用时大于此时长一半的用例会被标黄。
在套件中使用this.timeout(ms: number)可以用于终止超时的用例,测试结果会被视为错误,毫秒为单位。若设置this.timeout(0)表示不限制超时时间。
chai基本语法(BDD风格)
引自: https://www.chaijs.cn/api/bdd/ “chai官方文档”
chai中所有API都是链式API,类似promise中的.then/.catch。
个人认为chai中的链式API就像一句英文句子,遵循英文语法。
断言方式
BDD风格下使用expect/should进行断言。
expect(obj: object, label: string).to:
obj表示断言对象,label为断言失败时的提示文本。
obj.should:
obj表示断言对象。
常用API(详细API请阅读官方文档)
.not:
表示非。
.ok:
表示true。
.a(type: string)/.an(type: string):
表示类型判断。其中object类型的判断为an(元音)。类型可以是js有的所有类型(包括ES6新增的symbol)。
.equal(val: obj)
表示相等(==)。
.deep:
表示严格相等(===),但不判断引用,也就是说类似expect({ a: 1 }).to.deep.equal({ a: 1 }
的断言为真。
.nested:
后面只能链式调用.property()和.include(),使.property和.include中能使用.
和[]
解构目标。不能与.own连用。
.own:
后面链式调用.property()和.include(),只断言自身属性,不断言原型链上的属性。不能与.nested连用。
.any:
部分匹配目标有的属性(匹配到一个断言就为真)。
.all:
全匹配目标有的属性(全部匹配到断言才为真)。
.exist:
判断对象是否为null或undefined。
.empty:
判断对象是否为空(空字符串,空数组,空对象)。
.include(val: any):
判断目标是否包含给定数据,若目标为字符串行为类似indexOf()
,若目标为类数组(数组,Set,Map)行为类似includes()
,若目标为对象则判断val中所有属性(包括属性名和属性值)在目标中是否存在且相等。
.property(name: string)
判断目标(只能是对象)是否有某一属性,name表示属性名。
.above(val: number)
判断目标值是否大于val。
.least(val: number)
判断目标值是否不小于(大于等于)val。
.below(val: number)
判断目标值是否小于val。
.most(val: number)
判断目标值是否不大于(小于等于)val。
.within(start: number, end: number)
判断目标值是否处于start到end的区间。
Jest基本语法
主要可以看下面这两部分。
测试相关:
https://www.jestjs.cn/docs/api “Jest官方文档”
常用断言语句:
https://blog.csdn.net/haoaiqian/article/details/90721016 “Jest断言归纳”
Jest与mocha的对比
Jest个人看来和mocha+chai差不多,主要差别在断言库的实现细节上。Jest的断言库风格比较简练,相较之下chai的断言库链式调用可能比较长,但符合英文语言习惯。
大的方面来说的话,Jest基本不需要额外的依赖,测试率覆盖和可视化报告已经集成在Jest内了,如果不想自己写可视化方法可以引入jest-html-reporter依赖。mocha就单纯是一个测试库,断言等等其他的东西都需要额外依赖。
Vue-test-utils
引自: https://vue-test-utils.vuejs.org/zh/guides/ “Vue-test-utils中文文档”
主要用于在测试中实例化组件,获取组件属性等。
常用属性
vm:
用于获取挂载好的vue组件中的方法和属性。
常用方法
mount():
用于挂载当前组件实例。
shallowMount():
用于挂载当前组价及其子组件实例。
find():
返回匹配选择器的dom节点。
trigger():
触发监听事件。
setData():
设置实例对象中data的值并强制更新。
text():
返回实例对象中的文本内容(即所有标签中的文本节点)。
html():
返回实例对象中dom节点的html字符串。