爲保證代碼的質量,單元測試必不可少。本文記錄本身在學習單元測試過程當中的一些總結。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...