记录一些在为项目引入单元测试时的一些困惑,希望可以对社区的小伙伴们有所启迪,少走一些弯路少踩一些坑。
- jest, mocha, karma, chai, sinon, jsmine, vue-test-utils都是些什么东西?
- chai,sinon是什么?
- 为什么以spec.js命名?
-
如何为聊天的文字消息组件写单元测试?
- 运行在哪个目录下?
- karma.conf.js怎么看?
- 人生中第一次单元测试
- istanbul是什么?
- vue-test-utils的常用api?
- 前端的单元测试,到底该测什么?
jest, mocha, karma, chai, sinon, jsmine, vue-test-utils都是些什么东西?
名词 | 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项目,引入单元测试,大致思路已经有了:
测试框架:mocha
抹平环境:karma
断言库:chai
BDD库:jsmine
这并不是最终结果,测试vue单文件组件,当然少不了vue-test-utils,但是将它放在什么位置呢。
需要阅读vue-test-utils源码。
chai,sinon是什么?
chai是什么?
- Chai是一个node和浏览器可用的BDD/TDD断言库。
- Chai类似于Node内建API的assert。
- 三种常用风格:assert,expect或者should。
const chai = require('chai');
const assert = chai.assert;
const expect = chai.expect();
const should = chai.should();
sinon是什么?
- 一个 once函数,该如何测试这个函数?
- spy是什么?
function once(fn) {
var returnValue, called = false;
return function () {
if (!called) {
called = true;
returnValue = fn.apply(this, arguments);
}
return returnValue;
};
}
Fakes
it('calls the original function', function () {
var callback = sinon.fake();
var proxy = once(callback);
proxy();
assert(callback.called);
});
只调用一次更重要:
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和参数重要:
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返回的函数需要返回源函数的返回。为了测试这个,我们创建一个假行为:
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等等。
sinon.spy()?
test spy是一个函数,它记录所有的参数,返回值,this值和函数调用抛出的异常。
有3类spy:
- 匿名函数
- 具名函数
- 对象的方法
匿名函数
测试函数如何处理一个callback。
"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。这里有一个人为的例子:
{
setUp: function () {
sinon.spy(jQuery, "ajax");
},
tearDown: function () {
jQuery.ajax.restore();// 释放出spy
},
}
引申问题
BDD/TDD是什么?
What’s the difference between Unit Testing, TDD and BDD?
[[译]单元测试,TDD和BDD之间的区别是什么?](https://github.com/FrankKai/F...
为什么以spec.js命名?
SO上有这样一个问题:What does “spec” mean in Javascript Testing
spec是sepcification的缩写。
就测试而言,Specification指的是给定特性或者必须满足的应用的技术细节。最好的理解这个问题的方式是:让某一部分代码成功通过必须满足的规范。
如何为聊天的文字消息组件写单元测试?
运行在哪个文件夹下?
test文件夹下即可,文件名以.spec.js结尾即可,通过files和preprocessors中的配置可以匹配到。
karma.conf.js怎么看?
看不懂karma.conf.js,到 http://karma-runner.github.io... 学习配置。
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.... 添加切合vue项目的karma配置。
demo地址:https://github.com/eddyerburg...
人生中第一次单元测试
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 = ' 默认文字