学习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); }); });
如上测试代码就所有覆盖到了。