记录一些在为项目引入单元测试时的一些困惑,但愿能够对社区的小伙伴们有所启迪,少走一些弯路少踩一些坑。javascript
名词 | Github描述 | 我的理解 |
---|---|---|
jest | Delightful JavaScript Testing. Works out of the box for any React project.Capture snapshots of React trees | facebook家的测试框架,与react打配合会更加驾轻就熟一些。 |
mocha | Simple, flexible, fun JavaScript test framework for Node.js & The Browser | 强大的测试框架,中文名叫抹茶,常见的describe,beforeEach就来自这里 |
karma | A simple tool that allows you to execute JavaScript code in multiple real browsers. Karma is not a testing framework, nor an assertion library. Karma just launches an HTTP server, and generates the test runner HTML file you probably already know from your favourite testing framework. | 不是测试框架,也不是断言库,能够作到抹平浏览器障碍式的生成测试结果 |
chai | BDD / TDD assertion framework for node.js and the browser that can be paired with any testing framework. | BDD/TDD断言库,assert,expect,should比较有趣 |
sinon | Standalone and test framework agnostic JavaScript test spies, stubs and mocks | js mock测试框架,everything is fake,spy比较有趣 |
jsmine | Jasmine is a Behavior Driven Development testing framework for JavaScript. It does not rely on browsers, DOM, or any JavaScript framework. Thus it's suited for websites, Node.js projects, or anywhere that JavaScript can run. | js BDD测试框架 |
vue/test-utils | Utilities for testing Vue components | 专门为测试单文件组件而开发,学会使用vue-test-utils,将会在对vue的理解上更上一层楼 |
经过上述的表格,做为一个vue项目,引入单元测试,大体思路已经有了:html
测试框架:mocha
抹平环境:karma
断言库:chai
BDD库:jsmine
复制代码
这并非最终结果,测试vue单文件组件,固然少不了vue-test-utils,可是将它放在什么位置呢。 须要阅读vue-test-utils源码。前端
const chai = require('chai');
const assert = chai.assert;
const expect = chai.expect();
const should = chai.should();
复制代码
function once(fn) {
var returnValue, called = false;
return function () {
if (!called) {
called = true;
returnValue = fn.apply(this, arguments);
}
return returnValue;
};
}
复制代码
it('calls the original function', function () {
var callback = sinon.fake();
var proxy = once(callback);
proxy();
assert(callback.called);
});
复制代码
只调用一次更重要:vue
it('calls the original function only once', function () {
var callback = sinon.fake();
var proxy = once(callback);
proxy();
proxy();
assert(callback.calledOnce);
// ...or:
// assert.equals(callback.callCount, 1);
});
复制代码
并且咱们一样以为this和参数重要:java
it('calls original function with right this and args', function () {
var callback = sinon.fake();
var proxy = once(callback);
var obj = {};
proxy.call(obj, 1, 2, 3);
assert(callback.calledOn(obj));
assert(callback.calledWith(1, 2, 3));
});
复制代码
once返回的函数须要返回源函数的返回。为了测试这个,咱们建立一个假行为:node
it("returns the return value from the original function", function () {
var callback = sinon.fake.returns(42);
var proxy = once(callback);
assert.equals(proxy(), 42);
});
复制代码
一样还有 Testing Ajax,Fake XMLHttpRequest,Fake server,Faking time等等。react
test spy是一个函数,它记录全部的参数,返回值,this值和函数调用抛出的异常。 有3类spy:webpack
测试函数如何处理一个callback。ios
"test should call subscribers on publish": function() {
var callback = sinon.spy();
PubSub.subscribe("message", callback);
PubSub.publishSync("message");
assertTrue(callback.called);
}
复制代码
用spy包裹一个存在的方法。 sinon.spy(object,"method")
建立了一个包裹了已经存在的方法object.method的spy。这个spy会和源方法同样表现(包括用做构造函数时也是如此),可是你能够拥有数据调用的全部权限,用object.method.restore()能够释放出spy。这里有一我的为的例子:git
{
setUp: function () {
sinon.spy(jQuery, "ajax");
},
tearDown: function () {
jQuery.ajax.restore();// 释放出spy
},
}
复制代码
What’s the difference between Unit Testing, TDD and BDD? [译]单元测试,TDD和BDD之间的区别是什么?
SO上有这样一个问题:What does “spec” mean in Javascript Testing
spec是sepcification的缩写。
就测试而言,Specification指的是给定特性或者必须知足的应用的技术细节。最好的理解这个问题的方式是:让某一部分代码成功经过必须知足的规范。
test文件夹下便可,文件名以.spec.js结尾便可,经过files和preprocessors中的配置能够匹配到。
看不懂karma.conf.js,到 karma-runner.github.io/0.13/config… 学习配置。
const webpackConfig = require('../../build/webpack.test.conf');
module.exports = function karmaConfig(config) {
config.set({
browsers: ['PhantomJS'],// Chrome,ChromeCanary,PhantomJS,Firefox,Opera,IE,Safari,Chrome和PhantomJS已经在karma中内置,其他须要插件
frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],// ['jasmine','mocha','qunit']等等,须要额外经过NPM安装
reporters: ['spec', 'coverage'],// 默认值为progress,也能够是dots;growl,junit,teamcity或者coverage须要插件。spec须要安装karma-spec-reporter插件。
files: ['./index.js'],// 浏览器加载的文件, `'test/unit/*.spec.js',`等价于 `{pattern: 'test/unit/*.spec.js', watched: true, served: true, included: true}`。
preprocessors: {
'./index.js': ['webpack', 'sourcemap'],// 预处理加载的文件
},
webpack: webpackConfig,// webpack配置,karma会自动监控test的entry points
webpackMiddleware: {
noInfo: true, // webpack-dev-middleware配置
},
// 配置reporter
coverageReporter: {
dir: './coverage',
reporters: [{ type: 'lcov', subdir: '.' }, { type: 'text-summary' }],
},
});
};
复制代码
结合实际状况,经过https://vue-test-utils.vuejs.org/guides/testing-single-file-components-with-karma.html 添加切合vue项目的karma配置。
demo地址:github.com/eddyerburgh…
karma.conf.js
// This is a karma config file. For more details see
// http://karma-runner.github.io/0.13/config/configuration-file.html
// we are also using it with karma-webpack
// https://github.com/webpack/karma-webpack
const webpackConfig = require('../../build/webpack.test.conf');
module.exports = function karmaConfig(config) {
config.set({
// to run in additional browsers:
// 1. install corresponding karma launcher
// http://karma-runner.github.io/0.13/config/browsers.html
// 2. add it to the `browsers` array below.
browsers: ['Chrome'],
frameworks: ['mocha'],
reporters: ['spec', 'coverage'],
files: ['./specs/*.spec.js'],
preprocessors: {
'**/*.spec.js': ['webpack', 'sourcemap'],
},
webpack: webpackConfig,
webpackMiddleware: {
noInfo: true,
},
coverageReporter: {
dir: './coverage',
reporters: [{ type: 'lcov', subdir: '.' }, { type: 'text-summary' }],
},
});
};
复制代码
test/unit/specs/chat.spec.js
import { mount } from '@vue/test-utils';
import { expect } from 'chai';
import ChatText from '@/pages/chat/chatroom/view/components/text';
describe('ChatText.vue', () => {
it('人生中第一次单元测试:', () => {
const wrapper = mount(ChatText);
console.log(wrapper.html());
const subfix = '</p> <p>默认文字</p></div>';
expect(wrapper.html()).contain(subfix);
});
});
复制代码
注意,被测试组件必须以index.js暴露出组件。 NODE_ENV=testing karma start test/unit/karma.conf.js --single-run
测试结果:
1.PhantomJS是什么?
下载好phantomjs后,就能够在终端中模拟浏览器操做了。 foo.js
var page = require('webpage').create();
page.open('http://www.google.com', function() {
setTimeout(function() {
page.render('google.png');
phantom.exit();
}, 200);
});
复制代码
phantomjs foo.js
复制代码
运行上述代码后,会生成一张图片,可是画质感人。
啥玩意儿???一一搞定。
this.vm.$options.propsData // 组件的自定义属性,是由于2.1.x版本中没有$props对象,https://vue-test-utils.vuejs.org/zh/api/wrapper/#setprops-props
const elm = options.attachToDocument ? createElement() : undefined // "<div>" or undefined
slots // 传递一个slots对象到组件,用来测试slot是否生效的,值能够是组件,组件数组或者字符串,key是slot的name
mocks // 模拟全局注入
stubs // 存根子组件
复制代码
后知后觉,这些均可以在Mounting Options文档查看:vue-test-utils.vuejs.org/api/options…
mount仅仅挂载当前组件实例;而shallowMount挂载当前组件实例之外,还会挂载子组件。
这是一个一直困扰个人问题。 测试通用业务组件?业务变动快速,单元测试波动较大。❌ 测试用户行为?用户行为存在上下文关系,组合起来是一个很恐怖的数字,这个交给测试人员去测就行了。❌ 那我到底该测什么呢?要测试功能型组件,vue插件,二次封装的库。✔️
就拿我负责的项目来讲:
上述适用于单元测试的内容都有一个共同点:复用性高!
因此咱们在纠结要不要写单元测试时,抓住复用性高这个特色去考虑就行了。
单元测试是为了保证什么呢?
因此,其实单元测试是为了帮助开发者的突破本身心里的最后一道心理障碍,创建老子的代码彻底ojbk,不可能出问题的自信。
其实最终仍是保证用户有无bug的组件可用,有好的软件或平台使用,让本身的生活变得更加美好。
这是一个一直困扰个人问题。 测试通用业务组件?业务变动快速,单元测试波动较大。❌ 测试用户行为?用户行为存在上下文关系,组合起来是一个很恐怖的数字,这个交给测试人员去测就行了。❌ 那我到底该测什么呢?要测试功能型组件,vue插件,二次封装的库。✔️
就拿我负责的项目来讲:
功能型组件:可复用的上传组件,可编辑单元格组件,时间选择组件。(前两个组件都是老大写的,第三个是我实践中抽离出来的。) vue插件:mqtt.js,eventbus.js。(这两个组件是我抽象的。) 二次封装库:httpclient.js。(基于axios,老大初始化,我添砖加瓦。)
上述适用于单元测试的内容都有一个共同点:复用性高!
因此咱们在纠结要不要写单元测试时,抓住复用性高这个特色去考虑就行了。
单元测试是为了保证什么呢?
因此,其实单元测试是为了帮助开发者的突破本身心里的最后一道心理障碍,创建老子的代码彻底ojbk,不可能出问题的自信。
其实最终仍是保证用户有无bug的组件可用,有好的软件或平台使用,让本身的生活变得更加美好。
/* title: vue插件eventbus单测 author:frankkai target: 1.Vue.use(eventBus)是否能够正确注入$bus到prototype 2.注入的$bus是否能够成功挂载到组件实例 3.$bus是否能够正常订阅消息($on)和广播消息($emit) */
import eventbusPlugin from '@/plugins/bus';
import { createLocalVue, createWrapper } from '@vue/test-utils';
import { expect } from 'chai';
const localVue = createLocalVue();
localVue.use(eventbusPlugin);
const localVueInstance = (() =>
localVue.component('empty-vue-component', {
render(createElement) {
return createElement('div');
},
}))();
const Constructor = localVue.extend(localVueInstance);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
describe('/plugins/bus.js', () => {
it('Vue.use(eventBus)是否能够正确注入$bus到prototype:', () => {
expect('$bus' in localVue.prototype).to.equal(true);
});
it('注入的$bus是否能够成功挂载到组件实例:', () => {
expect('$bus' in wrapper.vm).to.equal(true);
});
it('$bus是否能够正常订阅消息($on)和广播消息($emit):', () => {
wrapper.vm.$bus.$on('foo', (payload) => {
expect(payload).to.equal('$bus emitted an foo event');
});
wrapper.vm.$bus.$on('bar', (payload) => {
expect(payload).to.equal('$bus emitted an bar event');
});
expect(Object.keys(vm.$bus._events).length).to.equal(2);
wrapper.vm.$bus.$emit('foo', '$bus emitted an foo event');
wrapper.vm.$bus.$emit('bar', '$bus emitted an bar event');
});
});
复制代码