为保证代码的质量,单元测试必不可少。本文记录本身在学习单元测试过程当中的一些总结。css
TDD属于测试驱动开发,BDD属于行为驱动开发。我的理解其实就是TDD先写测试模块,再写主功能代码,而后能让测试模块经过测试,而BDD是先写主功能模块,z再写测试模块。详见示例html
所谓服务端代码,指的就是一个node的模块,能在node的环境中运行。以一个项目为例,代码结构以下:前端
. ├── index.js ├── node_modules ├── package.json └── test └── test.js
前端测试框架主要是Mocha与Jasmine,这里咱们选择Mocha,断言库有should、expect、chai以及node自带的assert。这里咱们选择chai,chai中包含了expect、should及assert的书写风格。node
npm install mocha chai --save-dev
const getNum = (value) => { return value * 2 } module.exports = getNum
const chai = require('chai') const expect = chai.expect const getNum = require('../index') describe('Test', function() { it('should return 20 when the value is 10', function() { expect(getNum(10)).to.equal(20) }) })
describe用于给测试用例分组,it表明一个测试用例。webpack
"scripts": { "test": "mocha" }
须要在全局下安装Mochagit
npm install mocha -g
项目目录下执行es6
npm run test
测试经过github
完成代码测试以后咱们再去看看代码测试的覆盖率。测试代码覆盖率咱们选择使用istanbul,全局安装web
npm install -g istanbul
使用istanbul启动Mochachrome
istanbul cover _mocha
测试经过,覆盖率100%
行覆盖率(line coverage):是否每一行都执行了?
函数覆盖率(function coverage):是否每一个函数都调用了?
分支覆盖率(branch coverage):是否每一个if代码块都执行了?
语句覆盖率(statement coverage):是否每一个语句都执行了?
修改index.js再看代码覆盖率
const getNum = (value) => { if(value === 0) { return 1 }else { return value * 2 } } module.exports = getNum
发现代码覆盖率发生了变化
修改test.js添加测试用例
describe('Test', function() { it('should return 20 when the value is 10', function() { expect(getNum(10)).to.equal(20) }) it('should return 1 when the value is 0', function() { expect(getNum(0)).to.equal(0) }) })
代码覆盖率又回到了100%
客户端代码即运行在浏览器中的代码,代码中包含了window、document等对象,须要在浏览器环境下才能起做用。仍是以一个项目为例,代码结构以下:
. ├── index.js ├── node_modules ├── package.json └── test └── test.js └── test.html
咱们依然使用Mocha测试库及chai断言库。
npm install mocha chai --save-dev
window.createDiv = function(value) { var oDiv = document.createElement('div') oDiv.id = 'myDiv' oDiv.innerHTML = value document.body.appendChild(oDiv) }
mocha.ui('bdd') var expect = chai.expect describe("Tests", function () { before(function () { createDiv('test') }) it("content right", function () { var el = document.querySelector('#myDiv') expect(el).to.not.equal(null) expect(el.innerHTML).to.equal("test") }) }) mocha.run()
<html> <head> <title> Tests </title> <link rel="stylesheet" href="../node_modules/mocha/mocha.css"/> </head> <body> <div id="mocha"></div> <script src="../node_modules/mocha/mocha.js"></script> <script src="../node_modules/chai/chai.js"></script> <script src="../index.js"></script> <script src="./test.js"></script> </body> </html>
直接用浏览器打开test.html文件便能看到测试结果
固然咱们能够选择PhantomJS模拟浏览器去作测试,这里咱们使用mocha-phantomjs对test.html作测试。
全局安装mocha-phantomjs
npm install mocha-phantomjs -g
修改package.json
"scripts": { "test": "mocha-phantomjs test/test.html" }
项目目录下执行
npm run test
mocha-phantomjs在mac下执行会报phantomjs terminated with signal SIGSEGV
,暂时没有找到什么解决方案。因此我在ubuntu下执行,结果显示如图
上述方式虽然能完成代码的单元测试,可是要完成代码覆盖率的计算也没有什么好的方式,因此我选择引入测试管理工具karma
略去一大堆介绍karma的废话,项目下引入karma
npm install karma --save-dev
初始化配置karma配置文件
npm install karma -g karma init
使用karma默认配置看看每一项的做用
module.exports = function(config) { config.set({ basePath: '', // 设置根目录 frameworks: ['jasmine'], // 测试框架 files: [ // 浏览器中加载的文件 ], exclude: [ // 浏览器中加载的文件中排除的文件 ], preprocessors: { // 预处理 }, reporters: ['progress'], // 添加额外的插件 port: 9876, // 开启测试服务时监听的端口 colors: true, logLevel: config.LOG_INFO, autoWatch: true, // 监听文件变化,发生变化则从新编译 browsers: ['Chrome'], // 测试的浏览器 singleRun: false, // 执行测试用例后是否关闭测试服务 concurrency: Infinity }) }
此时的项目结构以下所示
. ├── index.js ├── node_modules ├── package.json ├── karma.conf.js └── test └── test.js
首先咱们将测试框架jasmine改成咱们熟悉的mocha及chai,添加files及plugins
npm install karma karma-mocha karma-chai mocha chai karma-chrome-launcher --save-dev
module.exports = function(config) { config.set({ basePath: '', frameworks: ['mocha', 'chai'], files: [ 'index.js', 'test/*.js' ], exclude: [ ], preprocessors: { }, reporters: ['progress'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false, concurrency: Infinity, plugins: [ 'karma-chrome-launcher', 'karma-mocha', 'karma-chai', ] }) }
全局安装karma-cli
npm install -g karma-cli
修改package.json
"scripts": { "test": "karma start karma.conf.js" }
执行npm run test即可以启用chrome去加载页面。
测试代码覆盖率,咱们仍是选择使用PhantomJS模拟浏览器,将singleRun
设为true
,即执行完测试用例就退出测试服务。
npm install karma-phantomjs-launcher --save-dev
将browsers
中的Chrome
改成PhantomJS
,plugins
中的karma-chrome-launcher
改成karma-phantomjs-launcher
执行npm run test ,测试经过且自动退出如图所示
引入karma代码覆盖率模块karma-coverage,改模块依赖于istanbul
npm install istanbul karma-coverage --save-dev
修改karma.conf.js
module.exports = function(config) { config.set({ basePath: '', frameworks: ['mocha', 'chai'], files: [ 'index.js', 'test/*.js' ], exclude: [ ], preprocessors: { 'index.js': ['coverage'] }, reporters: ['progress', 'coverage'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['PhantomJS'], singleRun: true, concurrency: Infinity, coverageReporter: { type : 'text-summary' }, plugins: [ 'karma-phantomjs-launcher', 'karma-mocha', 'karma-coverage', 'karma-chai', ] }) }
对index.js文件使用coverage进行预处理,加入karma-coverage插件,覆盖率测试输出coverageReporter配置,详见这里这里为了方便截图显示将其设为text-summary
执行npm run test,显示结果以下图所示
目前的浏览器并不能兼容全部ES6代码,因此ES6代码都须要通过babel编译后才可在浏览器环境中运行,但编译后的代码webpack会加入许多其余的模块,对编译后的代码作测试覆盖率就没什么意义了。
const createDiv = value => { var oDiv = document.createElement('div') oDiv.id = 'myDiv' oDiv.innerHTML = value document.body.appendChild(oDiv) } module.exports = createDiv
咱们使用ES6中的箭头函数
const createDiv = require('../index') describe("Tests", function () { before(function () { createDiv('test') }) it("content right", function () { var el = document.querySelector('#myDiv') expect(el).to.not.equal(null) expect(el.innerHTML).to.equal("test") }) })
module.exports = function(config) { config.set({ basePath: '', frameworks: ['mocha', 'chai'], files: [ 'test/*.js' ], exclude: [ ], preprocessors: { 'index.js': ['coverage'], 'test/*.js': ['webpack'] }, reporters: ['progress', 'coverage'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['PhantomJS'], singleRun: true, concurrency: Infinity, coverageReporter: { type : 'text-summary' }, webpack: { module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { "presets": ["es2015"], "plugins": [["istanbul"]] } } } ] } }, plugins: [ 'karma-phantomjs-launcher', 'karma-mocha', 'karma-coverage', 'karma-webpack', 'karma-chai', ] }) }
test.js文件经过require引入index.js文件,因此files只需引入test.js文件,再对test.js作webpack预处理。
须要相关的babel,webpack,karma依赖以下:
"devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-plugin-istanbul": "^4.1.5", "babel-preset-es2015": "^6.24.1", "chai": "^4.1.2", "istanbul": "^0.4.5", "karma": "^2.0.0", "karma-chai": "^0.1.0", "karma-coverage": "^1.1.1", "karma-mocha": "^1.3.0", "karma-phantomjs-launcher": "^1.0.4", "karma-webpack": "^2.0.9", "mocha": "^4.1.0", "webpack": "^3.10.0" }
执行npm run dev显示如图所示
travisCI的配置这里不作详解,咱们将经过代码测试覆盖率上传到coveralls获取一个covarage的icon。
若是你是服务端代码使用istanbul计算代码覆盖率的
language: node_js node_js: - 'stable' - 8 branches: only: - master install: - npm install script: - npm test after_script: "npm install coveralls && cat ./coverage/lcov.info | coveralls"
若是你是服务端代码使用karma计算代码覆盖率的,则需使用coveralls模块
npm install coveralls karma-coveralls --save-dev
// Karma configuration module.exports = function(config) { config.set({ basePath: '', frameworks: ['mocha', 'chai'], files: [ 'test/*.js' ], exclude: [], preprocessors: { 'test/*.js': ['webpack'], 'index.js': ['coverage'] }, reporters: ['progress', 'coverage', 'coveralls'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['PhantomJS'], singleRun: true, concurrency: Infinity, webpack: { module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { "presets": ["es2015"], "plugins": [["istanbul"], ["transform-runtime"]] } } } ] } }, coverageReporter: { type : 'lcov', dir : 'coverage/' }, plugins: [ 'karma-webpack', 'karma-phantomjs-launcher', 'karma-coverage', 'karma-mocha', 'karma-chai', 'karma-coveralls' ], }) }
plugins添加karma-coveralls,reporters添加coveralls,coverageReporter输出配置改成lcov。
能够参考我本身实现的一个show-toast库
https://github.com/tmallfe/tm...
https://codeutopia.net/blog/2...
https://github.com/jdavis/tdd...
https://jasmine.github.io/
https://github.com/bbraithwai...
https://mochajs.org/
https://toutiao.io/posts/5649...
https://coveralls.io/
https://karma-runner.github.i...
https://github.com/karma-runn...
https://shouldjs.github.io/
https://juejin.im/post/598073...
http://www.bradoncode.com/blo...
http://docs.casperjs.org/en/l...
https://github.com/gotwarlost...
https://www.jianshu.com/p/ffd...
http://www.bijishequ.com/deta...
http://phantomjs.org/
https://github.com/CurtisHump...
http://www.jackpu.com/shi-yon...
https://github.com/JackPu/Jav...
https://github.com/caitp/karm...
https://github.com/karma-runn...