为保证代码的质量,单元测试必不可少。本文记录自己在学习单元测试过程中的一些总结。
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
测试通过,覆盖率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
- 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>
直接用浏览器打开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的废话,项目下引入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...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。