爲保證代碼的質量,單元測試必不可少。本文記錄自己在學習單元測試過程中的一些總結。
TDD與BDD的區別
TDD屬於測試驅動開發,BDD屬於行爲驅動開發。個人理解其實就是TDD先寫測試模塊,再寫主功能代碼,然後能讓測試模塊通過測試,而BDD是先寫主功能模塊,z再寫測試模塊。詳見示例
服務端代碼測試
所謂服務端代碼,指的就是一個node的模塊,能在node的環境中運行。以一個項目爲例,代碼結構如下:
.
├── index.js
├── node_modules
├── package.json
└── test
└── test.js
前端測試框架主要是Mocha與Jasmine,這裏我們選擇Mocha,斷言庫有should、expect、chai以及node自帶的assert。這裏我們選擇chai,chai中包含了expect、should及assert的書寫風格。
npm install mocha chai --save-dev
index.js
const getNum = (value) => {
return value * 2
}
module.exports = getNum
test.js
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代表一個測試用例。
package.json
"scripts": {
"test": "mocha"
}
需要在全局下安裝Mocha
npm install mocha -g
項目目錄下執行
npm run test
完成代碼測試之後我們再去看看代碼測試的覆蓋率。測試代碼覆蓋率我們選擇使用istanbul,全局安裝
npm install -g istanbul
使用istanbul啓動Mocha
istanbul cover _mocha
行覆蓋率(line coverage):是否每一行都執行了?
函數覆蓋率(function coverage):是否每個函數都調用了?
分支覆蓋率(branch coverage):是否每個if代碼塊都執行了?
語句覆蓋率(statement coverage):是否每個語句都執行了?
客戶端代碼
客戶端代碼即運行在瀏覽器中的代碼,代碼中包含了window、document等對象,需要在瀏覽器環境下才能起作用。還是以一個項目爲例,代碼結構如下:
.
├── index.js
├── node_modules
├── package.json
└── test
└── test.js
└── test.html
我們依然使用Mocha測試庫及chai斷言庫。
npm install mocha chai --save-dev
index.js
window.createDiv = function(value) {
var oDiv = document.createElement('div')
oDiv.id = 'myDiv'
oDiv.innerHTML = value
document.body.appendChild(oDiv)
}
test.js
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()
test.html
<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>
當然我們可以選擇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的廢話,項目下引入karma
npm install karma --save-dev
初始化配置karma配置文件
npm install karma -g
karma init
使用karma默認配置看看每一項的作用
Karma.conf.js
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
karma.conf.js
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去加載頁面。
karma測試代碼覆蓋率
測試代碼覆蓋率,我們還是選擇使用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代碼,所以ES6代碼都需要經過babel編譯後纔可在瀏覽器環境中運行,但編譯後的代碼webpack會加入許多其他的模塊,對編譯後的代碼做測試覆蓋率就沒什麼意義了。
index.js
const createDiv = value => {
var oDiv = document.createElement('div')
oDiv.id = 'myDiv'
oDiv.innerHTML = value
document.body.appendChild(oDiv)
}
module.exports = createDiv
我們使用ES6中的箭頭函數
test.js
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")
})
})
kama.conf.js
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
travisCI的配置這裏不做詳解,我們將通過代碼測試覆蓋率上傳到coveralls獲取一個covarage的icon。
如果你是服務端代碼使用istanbul計算代碼覆蓋率的
.travis.yml
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.conf.js
// 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…