學習Karma+Jasmine+istanbul+webpack自動化單元測試javascript
1-1. 什麼是karma?
Karma 是一個基於Node.js的Javascript測試執行過程管理工具。該工具可用於測試全部主流web瀏覽器,也可集成到CI工具,也能夠
和其餘代碼編輯器一塊兒使用,它能夠監聽文件的變化,而後自動執行。html
1-2. 什麼是Jasmine?
Jasmine也是一款javascript測試框架。Jasmine官網文檔地址(https://jasmine.github.io/2.3/introduction.html)vue
1-3. 什麼是istanbul?
istanbul 是一個單元測試代碼覆蓋率檢查工具,它能夠直觀的告訴咱們,單元測試對代碼的控制程度。java
2. 安裝Karma環境
以下命令:node
npm install -g karma
爲了方便搭建karma環境,咱們能夠全局安裝karma-cli來幫咱們初始化測試環境。
以下命令安裝:jquery
npm install -g karma-cli
咱們在項目的根目錄下也須要安裝一下,以下命令:webpack
npm install karma --save-dev
如上,安裝完成之後,咱們在項目的根目錄下的命令行輸入命令:karma start
運行以下:git
karma start 08 02 2018 21:38:49.566:WARN [karma]: No captured browser, open http://localhost:9876/ 08 02 2018 21:38:49.572:INFO [karma]: Front-end scripts not present. Compiling... 08 02 2018 21:38:50.257:INFO [karma]: Karma v2.0.0 server started at http://0.0.0.0:9876/
而後咱們在瀏覽器輸入 http://localhost:9876, 以下圖所示:es6
若是出現以上信息,表示karma已經安裝成功了。github
3. karma的配置
命令以下:
karma init
對上面命令的說明以下:
1. 使用哪一個測試框架,咱們選擇了jasmine
2. 是否添加Require.js插件,咱們選擇no,不添加。
3. 選擇瀏覽器,咱們選擇了chrome。
4. 測試文件路徑設置,文件可使用通配符匹配,好比*.js匹配指定目錄下全部的js文件。
5. 在測試文件路徑下,須要被排除的文件。
6. 是否容許karma監測文件,yes表示當測試文件變化時候,karma會自動測試。
如上命令後,就會在項目的根目錄下 生成 karma.conf.js 文件;
下面對經常使用的 karma.conf.js 配置項進行解析說明以下:
// Karma configuration // Generated on Thu Feb 08 2018 10:39:50 GMT+0800 (CST) module.exports = function(config) { config.set({ // 將用於解析全部模式的基本路徑 basePath: '', // 選擇測試框架,咱們選擇 'jasmine' // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['jasmine'], // 在瀏覽器中加載的匹配的文件列表。 files: [ /* 注意:是本身添加的 */ 'src/**/*.js', 'test/**/*.js' ], // 要排除的文件列表 exclude: [],// 在將其提供給瀏覽器以前,預處理匹配的文件, // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { },// 怎麼顯示測試結果 // 測試結果顯示插件: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress'], // 服務器的端口號 port: 9876, // 在輸出中啓用/禁用顏色(記錄reporters和日誌), colors: true, // 顯示日誌記錄的級別(默認就好) // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // 當任何測試文件更改時候,啓用/禁用監聽文件並執行測試 autoWatch: true, // start these browsers 啓動的瀏覽器chrome // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['Chrome'], // 持續集成模式,默認就好 // if true, Karma captures browsers, runs the tests and exits singleRun: false, // 併發級別,能夠同時啓動多少個瀏覽器,默認無限大 // how many browser should be started simultaneous concurrency: Infinity }) }
如上代碼中的 config.set 中的 files 配置的 是我本身添加的;以下代碼:
files: [ /* 注意:是本身添加的 */ 'src/**/*.js', 'test/**/*.js' ]
它的做用是:就是把須要測試的文件都require進來,而後一股腦的在browsers裏面跑。
4. 開啓Karma
命令以下: karma start
手動打開chrome,輸入localhost:9876 便可打開了。
在控制檯命令行中看到以下信息 說明運行成功了。
~/我的demo/vue1204/karma-demo on Dev_20171115_wealth! $ karma start 08 02 2018 11:06:41.365:WARN [karma]: No captured browser, open http://localhost:9876/ 08 02 2018 11:06:41.379:INFO [karma]: Karma v2.0.0 server started at http://0.0.0.0:9876/ 08 02 2018 11:06:41.380:INFO [launcher]: Launching browser Chrome with unlimited concurrency 08 02 2018 11:06:41.386:INFO [launcher]: Starting browser Chrome 08 02 2018 11:06:42.457:INFO [Chrome 64.0.3282 (Mac OS X 10.11.6)]: Connected on socket JUZvceJFJhGgWFYJAAAA with id 7542403 LOG: 111 Chrome 64.0.3282 (Mac OS X 10.11.6): Executed 1 of 1 SUCCESS (0.011 secs / 0 secs)
運行後會自動打開chrome瀏覽器,http://localhost:9876/?id=50037613
先來安裝一下依賴的插件以下:
1. 須要能夠打開chrome瀏覽器的插件 npm install karma-chrome-launcher --save-dev
2. 須要能夠運行jasmine的插件 npm install karma-jasmine jasmine-core --save-dev
3. 須要能夠運行webpack的插件 npm install karma-webpack webpack --save-dev
4. 須要能夠顯示的sourcemap的插件 npm install karma-sourcemap-loader --save-dev
5. 須要能夠顯示測試代碼覆蓋率的插件 npm install karma-coverage-istanbul-reporter --save-dev
6. 須要全局安裝 jasmine-core 如命令:npm install -g jasmine-core
以下一鍵安裝命令:
npm install --save-dev karma-chrome-launcher karma-jasmine karma-webpack karma-sourcemap-loader karma-coverage-istanbul-reporter
也須要全局安裝一下 jasmine-core, 以下代碼:
npm install -g jasmine-core
咱們能夠寫一個簡單的測試用例;以下目錄結構:
karma-demo |---src | --index.js |--- test | |-- index.test.js | |--- karma.conf.js
src/index.js代碼以下:
function isNum(num) { if (typeof num === 'number') { return true; } else { return false; } }
test/index.test.js代碼以下:
describe('測試用例編寫', function() { it('isNum() should work fine', function() { console.log(111) expect(isNum(1)).toBe(true); expect(isNum('1')).toBe(false); }); });
如上代碼咱們能夠看到 在 test/index.test.js 裏面咱們調用了 isNum方法,可是並無使用require引用進來而可使用,那是由於
咱們的karma.conf.js裏面的配置文件 files裏面設置了,所以沒有使用require就可使用了。
files: [ /* 注意:是本身添加的 */ 'src/**/*.js', 'test/**/*.js' ]
它的做用是:就是把須要測試的文件都require進來,而後一股腦的在browsers裏面跑。
5. Coverage
如何測量測試腳本的質量呢?其中有一個參考指標就是代碼覆蓋率。
5-1:什麼是代碼覆蓋率?
代碼覆蓋率是在測試中運行到的代碼佔全部代碼的比率。所以接下來咱們在Karma環境中添加Coverage。
在項目的根目錄下,運行以下命令進行安裝
npm install --save-dev karma-coverage
而後須要在配置文件 karma.conf.js 代碼配置以下:
module.exports = function(config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['jasmine'], // list of files / patterns to load in the browser files: [ /* 注意:是本身添加的 */ 'src/**/*.js', 'test/**/*.js' ], // list of files / patterns to exclude exclude: [], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor /* 覆蓋源文件 不包括測試庫文件*/ preprocessors: { 'src/**/*.js': ['coverage'] }, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress', 'coverage'], /* 新增的配置項 */ coverageReporter: { type: 'html', dir: 'coverage/' }, // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: true, // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['Chrome'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false, // Concurrency level // how many browser should be started simultaneous concurrency: Infinity }) }
具體的配置能夠參考 https://www.npmjs.com/package/karma-coverage
再運行下 karma start後,會在根目錄下生產 coverage目錄,裏面有index.html做爲本次的測試報告,咱們打開看下,以下:
如上代碼的覆蓋率是對源文件須要被測試的覆蓋率是100%;
要生成代碼覆蓋率,能夠看這篇文章(http://karma-runner.github.io/0.8/config/coverage.html)
想要生成覆蓋率,須要在配置項配置以下三個選項:
1. preprocessors coverage (必須配置的)
2. reporters coverage (必須配置的)
3. reporter options (可選的)
1. Preprocessors(配置預處理器)
preprocessors 的含義是:是那些測試文件須要被覆蓋,好比,若是全部代碼都在src/下的文件 您須要添加到您的配置文件,如上
配置代碼:
preprocessors: { 'src/**/*.js': ['coverage'] }
注意: 不要包含你所依賴的庫,測試文件等等,下面就是一個錯誤的配置信息。
files = [ JASMINE, JASMINE_ADAPTER, 'lib/*.js', 'test/*.js' ]; preprocessors = { '**/*.js': 'coverage' };
2. Reporters(配置報告)
在配置文件中包含下面的信息來激活覆蓋率報告器。
如上配置代碼:
reporters: ['progress', 'coverage'],
這樣將會對每一個瀏覽器建立一個覆蓋率報告,另外,它還會建立一個 Json 文件,其中包含輸出的中間數據。
3. Reporter Options(配置報告選項)
默認的報告格式以下:
coverageReporter: { type: 'html', dir: 'coverage/' },
type 是一個字符串值,取值能夠是:
html (default)
lcov (lcov and html)
lcovonly
text
text-summary
cobertura (xml format supported by Jenkins)
dir 則用來配置報告的輸出目錄。
若是類型是 text 或者 text-summary,你能夠配置 file 參數來指定保存的文件名。
coverageReporter = { type : 'text', dir : 'coverage/', file : 'coverage.txt' }
若是沒有文件名,就會輸出到控制檯。
6. webpack和Babel集成Karma環境中。
在項目中,會使用到webpack和es6,所以須要集成到karma環境中。
安裝karma-webpack
npm install --save-dev karma-webpack webpack
1.安裝babel核心文件 npm install babel-core --save-dev
2. webpack的Loader處理器 npm install babel-loader --save-dev
3. babel的istanbul覆蓋率插件 npm install babel-plugin-istanbul --save-dev
4. babel轉換到哪一個版本這裏是ES2015 npm install babel-preset-es2015 --save-dev
一鍵安裝命令以下:
npm install --save-dev babel-loader babel-core babel-preset-es2015 babel-plugin-istanbul
而後 src/index.js 代碼變成以下:
function isNum(num) { if (typeof num === 'number') { return true; } else { return false; } } module.exports = isNum;
test/index.test.js 變成以下:
const isNum = require('../src/index'); describe('測試webpack+babel集成到Karma中', () => { it('isNum() should work fine.', () => { expect(isNum(1)).toBe(true); expect(isNum('1')).toBe(false); }) });
接下來修改配置文件karma.conf.js 以下配置代碼:
module.exports = function(config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['jasmine'], // list of files / patterns to load in the browser files: [ /* 注意:是本身添加的 */ 'test/**/*.js' ], // list of files / patterns to exclude exclude: [], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { 'test/**/*.js': ['webpack', 'coverage'] }, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress', 'coverage'], /* 新增的配置項 */ coverageReporter: { type: 'html', dir: 'coverage/' }, // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: true, // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['Chrome'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false, // Concurrency level // how many browser should be started simultaneous concurrency: Infinity, webpack: { module: { loaders: [{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, query: { presets: ['es2015'] } }] } } }) }
如上代碼修改的地方:
1. files只留下test文件。由於webpack會自動把須要的其它文件都打包進來,因此只須要留下入口文件。
2. preprocessors也修改成test文件,並加入webpack域處理器。
3. 加入webpack配置選項。能夠本身定製配置項,可是不須要entry和output。這裏加上babel-loader來編譯ES6代碼
命令行運行karma start,成功了~
可是咱們再來看看 coverage/xx/index.html
如上圖測試覆蓋率 不是100%;
緣由是webpack會加入一些代碼,影響了代碼的Coverage。若是咱們引入了一些其它的庫,好比jquery之類的,將源代碼和庫代碼打包在一塊兒後,覆蓋率會更難看。
所以咱們須要安裝以下插件來解決上面的問題。
istanbul的介紹:
istanbul是一個單元測試代碼覆蓋率檢查工具,能夠很直觀地告訴咱們,單元測試對代碼的控制程度。
1. webpack的Loader處理器 npm install istanbul-instrumenter-loader --save-dev
2. 測試覆蓋率顯示插件 npm install karma-coverage-istanbul-reporter --save-dev
而後咱們去修改 karma.conf.js
webpack: { module: { rules: [ { test: /\.js$/, use: { loader: 'babel-loader', options: { presets: ['es2015'], plugins: ['istanbul'] } }, exclude: /node_modules/ } ] } }
先給babel加上插件 plugins: ['istanbul'];
再寫上 istanbul-instrumenter-loader 的配置,因此整個配置的代碼變成以下:
而後把 karma.conf.js 配置改爲以下:
webpack: { module: { rules: [ { test: /\.js$/, use: { loader: 'istanbul-instrumenter-loader', options: { esModules: true } }, enforce: 'pre', exclude: /node_modules/ }, { test: /\.js$/, use: { loader: 'babel-loader', options: { presets: ['es2015'], plugins: ['istanbul'] } }, exclude: /node_modules/ } ] } }
具體能夠看 https://doc.webpack-china.org/loaders/istanbul-instrumenter-loader 的API。
所以全部的karma.conf.js 的配置代碼以下:
module.exports = function(config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['jasmine'], // list of files / patterns to load in the browser files: [ /* 注意:是本身添加的 */ 'test/**/*.js' ], // list of files / patterns to exclude exclude: [], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { 'test/**/*.js': ['webpack'] }, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress', 'coverage'], /* 新增的配置項 */ coverageReporter: { type: 'html', dir: 'coverage/' }, // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: true, // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['Chrome'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false, // Concurrency level // how many browser should be started simultaneous concurrency: Infinity, webpack: { module: { rules: [ { test: /\.js$/, use: { loader: 'istanbul-instrumenter-loader', options: { esModules: true } }, enforce: 'pre', exclude: /node_modules/ }, { test: /\.js$/, use: { loader: 'babel-loader', options: { presets: ['es2015'], plugins: ['istanbul'] } }, exclude: /node_modules/ } ] } } }) }
再運行 karma start 便可。
7. 怎麼測試覆蓋率
覆蓋率它有四個測量維度。
1. 行覆蓋率(line coverage): 是否每一行都執行了?
2. 函數覆蓋率(function coverage): 是否每一個函數都調用了?
3. 分支覆蓋率 (branch coverage): 是否每一個if代碼都執行了?
4. 語句覆蓋率(statement coverage): 是否每一個語句都執行了?
karma-demo |---src | |--index.js | |--- add.js |--- test | |-- index.test.js | |-- add.test.js | |--- karma.conf.js
如上demo,咱們在項目中的src路徑下新增add.js代碼以下:
function add (num1, num2) { return num1 + num2; } module.exports = add;
在test/add.test.js代碼以下:
const add = require('../src/add'); describe('加法運算', () => { it('測試簡單的兩個數相加', () => { expect(add(1, 1)).toBe(2); }) });
而後咱們繼續運行 karma start 後,會生成 coverage / xx/ index.html 運行結果以下:
如今咱們將add.js代碼變複雜點,若是不寫num2, 就默認爲0,以下代碼:
function add (num1, num2) { if (num2 === undefined) { num2 = 0; } return num1 + num2; } module.exports = add;
test/add.test.js 代碼以下:
const add = require('../src/add'); describe('第二個測試套件', function() { it('第一個測試用例: 1+1 === 2', function() { expect(add(1)).toBe(2); }); });
咱們繼續karma start後,再打開 coverage下的index.html文件變爲以下:
測試結果分析:
1個分支覆蓋率(branch coverage)沒有覆蓋到,1個函數和1個語句被覆蓋到,4行行覆蓋率所有被覆蓋。
咱們再繼續對add.js代碼進行改造。
function add (num1, num2) { if (num1 === undefined) { num1 = 0; } if (num2 === undefined) { num2 = 0; } return num1 + num2; } module.exports = add;
add.test.js 代碼測試以下:
const add = require('../src/add'); describe('第二個測試套件', function() { it('第1個測試用例: 1+1 === 2', function() { expect(add(1, 1)).toBe(2); }); it('第2個測試用例: 1+1 === 2', function() { expect(add()).toBe(0); }); it('第3個測試用例: 1+1 === 2', function() { expect(add(1)).toBe(1); }); it('第4個測試用例: 1+1 === 2', function() { expect(add(2, 1)).toBe(3); }); });
如上測試代碼就所有覆蓋到了。