2年前寫過一篇文章用Karma和QUnit作前端自動單元測試,只是大概講解了 karma 如何使用,針對的測試狀況是傳統的頁面模式。本文中題目中【現代】兩字代表了這篇文章對比以前的最大不一樣,最近幾年隨着SPA
(Single Page Application) 技術和各類組件化解決方案(Vue/React/Angular)的普及,咱們開發的應用的組織方式和複雜度已經發生了翻天覆地的變化,對應咱們的測試方式也必須跟上時代的發展。如今咱們一步一步把各類不一樣的技術結合一塊兒來完成頁面的單元測試和 e2e 測試。javascript
No API is the best API
。錯誤顯示異常清晰,自帶完整的自描述性。1) Array #indexOf() should return index when the value is present:
AssertionError: # path/to/test/mocha_node.js:10
assert(ary.indexOf(zero) === two)
| | | | |
| | | | 2
| -1 0 false
[1,2,3]
[number] two
=> 2
[number] ary.indexOf(zero)
=> -1
複製代碼
如下全部命令假設在 test-demo 項目下進行操做。css
# 爲了操做方便在全局安裝命令行支持
~/test-demo $ npm install karma-cli -g
# 安裝 karma 包以及其餘須要的插件和庫,這裏不一一闡述每一個庫的做用
~/test-demo $ npm install karma mocha power-assert karma-chrome-launcher karma-mocha karma-power-assert karma-spec-reporter karma-espower-preprocessor cross-env -D
# 建立測試目錄
~/test-demo $ mkdir test
# 初始化 karma
~/test-demo $ karma init ./test/karma.conf.js
複製代碼
執行初始化過程按照提示進行選擇和輸入html
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> mocha
Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no
Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> Chrome
>
What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
>
Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>
Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> no
複製代碼
生成的配置文件略做修改,以下(因篇幅緣由,隱藏了註釋):前端
module.exports = function(config) {
config.set({
basePath: '',
// 表示能夠在測試文件中不需引入便可使用兩個庫的全局方法
frameworks: ['mocha', 'power-assert'],
files: [
'../src/utils.js',
'./specs/utils.spec.js.js'
],
exclude: [
],
preprocessors: {
'./specs/utils.spec.js': ['espower']
},
reporters: ['spec'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
browsers: ['Chrome'],
singleRun: false,
concurrency: Infinity
})
}
複製代碼
咱們把源文件放在src
目錄下。vue
// src/utils.js
function reverseString(string) {
return string.split('').reverse().join('');
}
複製代碼
測試代碼放在test/specs
目錄下,每一個測試文件以 .spec.js
做爲後綴。java
// test/spes/utils.spec.js
describe('first test', function() {
it('test string reverse => true', function() {
assert(reverseString('abc') === 'cba');
});
it('test string reverse => false', function() {
assert(reverseString('abc') === 'cba1');
});
});
複製代碼
回到項目根目錄,運行命令 npm run test
開始執行測試,而後看到瀏覽器會自動打開執行測試,命令行輸出結果以下:node
[karma]: Karma v2.0.0 server started at http://0.0.0.0:9876/
[launcher]: Launching browser Chrome with unlimited concurrency
[launcher]: Starting browser Chrome
[Chrome 63.0.3239 (Mac OS X 10.13.1)]: Connected on socket HEw50fXV-d24BZGBAAAA with id 24095855
first test
✓ test string reverse => true
✗ test string reverse => false
AssertionError: # utils.spec.js:9
assert(reverseString('abc') === 'cba1')
| |
"cba" false
--- [string] 'cba1'
+++ [string] reverseString('abc')
@@ -1,4 +1,3 @@
cba
-1
Chrome 63.0.3239 (Mac OS X 10.13.1): Executed 2 of 2 (1 FAILED) (0.022 secs / 0.014 secs)
TOTAL: 1 FAILED, 1 SUCCESS
複製代碼
能夠看出一個測試成功一個測試失敗。webpack
測試覆蓋率是衡量測試質量的主要標準之一,含義是當前測試對於源代碼的執行覆蓋程度。在 karma
中使用 karma-coverage
插件便可輸出測試覆蓋率,插件底層使用的是 istanbul。git
~/test-demo $ npm i karma-coverage -D
複製代碼
修改 karma.conf.js
文件:github
preprocessors: {
'../src/utils.js': ['coverage'],
'./specs/utils.spec.js': ['espower']
},
reporters: ['spec', 'coverage'],
coverageReporter: {
dir: './coverage', // 覆蓋率結果文件放在 test/coverage 文件夾中
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' }
]
},
複製代碼
再次運行測試命令,在最後會輸出測試覆蓋率信息
=============================== Coverage summary ===============================
Statements : 100% ( 2/2 )
Branches : 100% ( 0/0 )
Functions : 100% ( 1/1 )
Lines : 100% ( 2/2 )
================================================================================
複製代碼
打開 test/coverage/lcov-report/index.html
網頁能夠看到詳細數據
上面的例子,只能用於測試使用傳統方式編寫的 js 文件。爲了模塊化和組件化,咱們可能會使用ES6
、commonjs
、AMD
等模塊化方案,而後使用 webpack 的 umd 打包方式輸出模塊以兼容不一樣的使用方式。通常咱們還須要使用ES6+
的新語法,須要在 webpack 中加入babel
做爲轉譯插件。
webpack 和 babel 的使用以及須要的依賴和配置,這裏不作詳細說明,由於主要是按照項目須要走,本文僅指出爲了測試而須要修改的地方。
~/test-demo $ npm i babel-plugin-istanbul babel-preset-power-assert karma-sourcemap-loader karma-webpack -D
複製代碼
.babelrc
把power-assert
以及coverage
的代碼注入修改成在babel
編譯階段進行,在.babelrc
文件中加入如下配置:
{
"env": {
"test": {
"presets": ["env", "babel-preset-power-assert"],
"plugins": ["istanbul"]
}
}
}
複製代碼
test/index.js
在測試文件以及源碼文件都很是多的狀況下,或者咱們想讓咱們的測試代碼也使用上ES6+
的語法和功能,咱們能夠創建一個入口來統一引入這些文件,而後使用 webpack 處理整個入口,在test
目錄下新建index.js
:
// require all test files (files that ends with .spec.js)
const testsContext = require.context('./specs', true, /\.spec$/)
testsContext.keys().forEach(testsContext)
// require all src files except main.js for coverage.
// you can also change this to match only the subset of files that
// you want coverage for.
const srcContext = require.context('../src', true, /^\.\/(?!main(\.js)?$)/)
srcContext.keys().forEach(srcContext)
複製代碼
karma.conf.js
修改已經增長對應的配置
{
files: [
'./index.js'
],
preprocessors: {
'./index.js': ['webpack', 'sourcemap'],
},
webpack: webpackConfig,
webpackMiddleware: {
noInfo: false
},
}
複製代碼
utils.spec.js
import reverseString from '../../src/utils';
describe('first test', function() {
it('test string reverse => true', function() {
assert(reverseString('abc') === 'cba');
});
it('test string reverse => false', function() {
assert(reverseString('abc') === 'cba1');
});
});
複製代碼
運行測試,能獲得和第二步相同的結果。
若是項目中使用了 vue,咱們想對封裝的組件進行測試,也很是簡單。
首先 webpack 配置中添加處理 vue 的邏輯,安裝須要的依賴,這裏再也不贅述。
在src
目錄下添加HelloWorld.vue
:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h2>Essential Links</h2>
</div>
</template>
<script> export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } } } </script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss" scoped> h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
複製代碼
而後添加測試代碼:
// test/specs/vue.spec.js
import Vue from 'vue';
import HelloWorld from '@/HelloWorld';
describe('HelloWorld.vue', () => {
it('should render correct contents', () => {
const Constructor = Vue.extend(HelloWorld)
const vm = new Constructor().$mount()
assert(vm.$el.querySelector('.hello h1').textContent === 'Welcome to Your Vue.js App')
})
})
複製代碼
運行測試,能夠看到命令行輸出:
first test
✓ test string reverse => true
✗ test string reverse => false
AssertionError: # test/specs/utils.spec.js:9
assert(reverseString('abc') === 'cba1')
| |
"cba" false
--- [string] 'cba1'
+++ [string] reverseString('abc')
@@ -1,4 +1,3 @@
cba
-1
HelloWorld.vue
✓ should render correct contents
複製代碼
這裏 Vue 能替換爲其餘任意的前端框架,只須要按照對應框架的配置能正確打包便可。
上面全部代碼都放在了這個項目,能夠把項目下載下來手動執行查看結果。
以上大概講解了現代前端測試的方法和過程,可是有人會問,咱們爲何須要搞那麼多事情,寫那麼多代碼甚至測試代碼比真實代碼還要多呢?這裏引用 Egg 官方一段話回答這個問題:
先問咱們本身如下幾個問題:
- 你的代碼質量如何度量?
- 你是如何保證代碼質量?
- 你敢隨時重構代碼嗎?
- 你是如何確保重構的代碼依然保持正確性?
- 你是否有足夠信心在沒有測試的狀況下隨時發佈你的代碼?
若是答案都比較猶豫,那麼就證實咱們很是須要單元測試。
它能帶給咱們不少保障:
- 代碼質量持續有保障
- 重構正確性保障
- 加強自信心
- 自動化運行
Web 應用中的單元測試更加劇要,在 Web 產品快速迭代的時期,每一個測試用例都給應用的穩定性提供了一層保障。 API 升級,測試用例能夠很好地檢查代碼是否向下兼容。 對於各類可能的輸入,一旦測試覆蓋,都能明確它的輸出。 代碼改動後,能夠經過測試結果判斷代碼的改動是否影響已肯定的結果。
複製代碼
是否是消除了不少心中的疑惑?
以上內容若有錯漏,或者有其餘見解,請留言共同探討。
版權聲明:原創文章,歡迎轉載,交流 ,請註明出處「本文發表於xlaoyu.info「。