在自己写组件时,验证组件是否可以使用,通常是放在页面当中,但是这样比较繁琐,需要自己用鼠标一个去触发,效率很低。如果需求有一点改动,那么组件还能不能正常运行,这是一个未知的事情。
为保证组件质量,就需要引入单元测试,当需求改变时,单元测试可能就编译不通过,就能第一时间修改bug
,保证组件质量。
单元测试
单元测试一般分为两种BDD
和TDD
:
BDD
:行为驱动开发,Behavior-driven development
TDD
:测试驱动开发,Test-driven development
行为驱动开发是用来模拟用户行为,用自然语言描述需求;测试驱动开发是完成产品需求;
单元测试中有一个概念Assert
,中文意思是断言,就是说我主观臆断认为是对的,如果断言正确,啥事也不会发生,否则会报错。
安装
这里使用的工具是:
Karma
是一个测试运行器,它可以呼起浏览器,加载测试脚本,然后运行测试用例Mocha
是一个单元测试框架/库,它可以用来写测试用例Sinon
是一个spy
/stub
/mock
库,用以辅助测试
安装相关插件:
npm i -D karma karma-chrome-launcher karma-mocha karma-sinon-chai mocha sinon sinon-chai karma-chai karma-chai-spies
在根目录下新建一个karma.conf.js
,其中files
中需要将css
也填进来,否则样式这块无法测试。
module.exports = function (config) {
config.set({
basePath: "",
frameworks: ["mocha", "sinon-chai"],
client: {
chai: {
includeStack: true,
},
},
files: ["dist/**/*.test.js", "dist/**/*.test.css"],
exclude: [],
preprocessors: {},
reporters: ["progress"],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ["ChromeHeadless"],
singleRun: false,
concurrency: Infinity,
});
};
编写单元测试
组件单元测试编写分为 4 个部分:
- 页面中组件是否存在
- 组件的属性是否设置正确
- 组件的样式是否加载正确
- 组件的事件是否触发
准备工作
在编写测试用例之前,引入相关依赖
const expect = chai.expect;
import Vue from "vue";
import Button from "../src/button";
单元测试中的几个概念
describe
是一个用例测试集,可以进行嵌套
describe("Button", () => {
// 进行 Button 相关的单元测试
});
一个it
对应一个单元测试用例
it("", () => {
// 相关测试代码
});
only
、skip
only
在当前的父 describe 块下,只执行该单元的测试skip
在当前的父 describe 块下,跳过该单元的测试
describe("Button", () => {
describe.only("父describe块下只执行该测试单元", () => {
it.skip("跳过的测试单元", () => {});
});
});
页面中组件是否存在
it("页面中组件是否存在", () => {
except(Button).to.be.ok; // 检测页面中是否有组件
});
组件的属性是否设置正确
Button
组件属性icon
,是用来设置Button
中的图标Button
组件属性loading
,是用来设置Button
中的加载图标,它和icon
只能出现一个。
通过这个用例分析,可以知道这里要写两个测试用例。
用例 1:
it("可以设置 icon", () => {
const Constructor = Vue.extend(Button);
const vm = new Constructor({
propsData: {
icon: "settings",
},
}).$mount(); // 挂载,在内存中
const useElement = vm.$el.querySelector("use"); // 获取到 use 元素
expect(useElement.getAttribute("xlink:href")).to.eq("#i-settings"); // 获取 use 元素的属性,用断言检测 xlink:href 是否为 #i-settings
vm.$destroy();
});
用例 2:
it("设置 loading", () => {
const Constructor = Vue.extend(Button);
const vm = new Constructor({
propsData: {
loading: true,
icon: "settings",
},
}).$mount();
const useElement = vm.$el.querySelectorAll("use"); // 获取所有 use 元素
expect(useElement.length).to.equal(1); // 检测 use 元素的数量等不等于 1,因为 loading 和 settings 只能出现一个
expect(useElement[0].getAttribute("xlink:href")).to.eq("#i-loading"); // 获取 use 元素的属性,用断言检测 xlink:href 是否为 #i-loading
vm.$destroy();
});
组件的样式是否加载正确
icon
可能会出现在文字的左边或者右边,组件中我们是通过css
去控制的,在编写测试用例时,就要检测样式是否正确。
icon
默认样式的order
为1
- 当设置了
iconPosition
后,icon
的order
为2
样式测试,需要挂载到页面中才可以,这里编写测试用例,需要将组件挂载到页面中
用例 1:
it("icon 默认 order 为 1", () => {
const div = document.createElement("div");
document.body.appendChild(div);
const Constructor = Vue.extend(Button);
const vm = new Constructor({
propsData: {
icon: "setting",
},
}).$mount(div); // 挂载到页面中
const svgElement = vm.$el.querySelector("svg");
expect(getComputedStyle(svgElement).order).to.eq("1"); // 检测 svg 元素的样式 order 默认值是不是 1
vm.$el.remove(); // 挂载到页面中的元素,需要被销毁
vm.$destroy();
});
用例 2:
it("设置 iconPosition 可以改变 order", () => {
const div = document.createElement("div");
document.body.appendChild(div);
const Constructor = Vue.extend(Button);
const vm = new Constructor({
propsData: {
icon: "settings",
iconPosition: "right", // 设置 iconPosition 为 right
},
}).$mount(div);
const svgElement = vm.$el.querySelector("svg");
expect(getComputedStyle(svgElement).order).to.eq("2"); // 设置了 iconPosition 为 right 后,检测 svg 元素的样式 order 默认值是不是 1
vm.$el.remove();
vm.$destroy();
});
组件的事件是否触发
it("点击 button 触发 click 事件", () => {
const Constructor = Vue.extend(Button);
const vm = new Constructor({
propsData: {
icon: "settings",
},
}).$mount();
const callback = sinon.fake(); // 用 sinon 创建一个函数
vm.$on("click", callback); // 绑定事件
vm.$el.click(); // 点击调用
expect(callback).to.have.been.called; // 检测点击时是否被调用
vm.$destroy();
});
另外可添加微信ttxbg180218
交流