Karma 是 Goolge 开源的一个 Test runner, 可以配合 Grunt 使用。
1. 相关插件介绍
1.1 Karma 的官网
http://karma-runner.github.io/
官网中的文档其实分多钟版本,不同版本的 karma 使用也有所不同,注意页面右上角的版本信息。
1.2 配合 Grunt 的插件 grunt-karma
https://github.com/karma-runner/grunt-karma
1.3 karma 用来启动 chrome 的 karma 插件
https://github.com/karma-runner/karma-chrome-launcher
1.4 jasmine 单元测试插件
https://github.com/karma-runner/karma-jasmine
1.5 单元测试的报表格式
单元测试默认提供了两种输出:‘dots‘, ‘progress‘ ,都比较简单,希望看到测试的详细输出,可以使用 reporter 插件,这里是其中之一:mocha
https://github.com/litixsoft/karma-mocha-reporter
1.6 angularJS 插件
https://github.com/karma-runner/karma-ng-scenario
1.7 requirejs 模块化插件
https://github.com/karma-runner/karma-requirejs
2. 安装插件
2.1 创建 package.json
使用 npm init 来创建 package.json 项目文件。
PS C:\study\demo> npm init This utility will walk you through creating a package.json file. It only covers the most common items, and tries to guess sensible defaults. See `npm help json` for definitive documentation on these fields and exactly what they do. Use `npm install <pkg> --save` afterwards to install a package and save it as a dependency in the package.json file. Press ^C at any time to quit. name: (demo) demo version: (1.0.0) description: entry point: (index.js) test command: git repository: keywords: author: license: (ISC) MIT About to write to C:\study\demo\package.json: { "name": "demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "MIT" } Is this ok? (yes) y PS C:\study\demo>
安装 grunt-karma 插件。
PS C:\study\demo> npm install grunt-karma --save-dev npm WARN package.json [email protected]1.0.0 No description npm WARN package.json [email protected]1.0.0 No repository field. npm WARN package.json [email protected]1.0.0 No README data grunt[email protected]0.12.1 node_modules\grunt-karma └── [email protected]3.10.1
安装之后,在 node_modules 中实际上有了 grunt, grunt-karma, 以及 karma 三个组件。
继续安装 karma 的插件。
先安装 chrome 的启动器。
PS C:\study\demo> npm install --save-dev karma-chrome-launcher npm WARN package.json [email protected]1.0.0 No description npm WARN package.json [email protected]1.0.0 No repository field. npm WARN package.json [email protected]1.0.0 No README data karma[email protected]0.2.0 node_modules\karma-chrome-launcher ├── fs[email protected]1.0.0 (null[email protected]1.0.0) └── which@1.1.2 ([email protected]0.1.7)
单元测试 jasmine.
PS C:\study\demo> npm install --save-dev karma-jasmine npm WARN package.json [email protected]1.0.0 No description npm WARN package.json [email protected]1.0.0 No repository field. npm WARN package.json [email protected]1.0.0 No README data npm WARN peerDependencies The peer dependency jasmine[email protected]* included from karma-jasmine will no npm WARN peerDependencies longer be automatically installed to fulfill the peerDependency npm WARN peerDependencies in npm 3+. Your application will need to depend on it explicitly. jasmine[email protected]2.3.4 node_modules\jasmine-core karma[email protected]0.3.6 node_modules\karma-jasmine
单元测试的输出报表格式 mocha.
PS C:\study\demo> npm install --save-dev karma-mocha-reporter npm WARN package.json [email protected]1.0.0 No description npm WARN package.json [email protected]1.0.0 No repository field. npm WARN package.json [email protected]1.0.0 No README data karma[email protected]1.1.1 node_modules\karma-mocha-reporter └── [email protected]1.1.0 (escape-string[email protected]1.0.3, [email protected]2.0.0, [email protected]2.1.0, [email protected]2.0.0, [email protected]3.0.0)
angular 插件
PS C:\study\demo> npm install --save-dev karma-ng-scenario npm WARN package.json [email protected]1.0.0 No description npm WARN package.json [email protected]1.0.0 No repository field. npm WARN package.json [email protected]1.0.0 No README data karma[email protected]0.1.0 node_modules\karma-ng-scenario
requirejs 插件。
PS C:\study\demo> npm install --save-dev karma-requirejs npm WARN package.json [email protected]1.0.0 No description npm WARN package.json [email protected]1.0.0 No repository field. npm WARN package.json [email protected]1.0.0 No README data npm WARN peerDependencies The peer dependency [email protected]~2.1 included from karma-requirejs will no npm WARN peerDependencies longer be automatically installed to fulfill the peerDependency npm WARN peerDependencies in npm 3+. Your application will need to depend on it explicitly. [email protected]2.1.20 node_modules\requirejs karma[email protected]0.2.2 node_modules\karma-requirejs
3. 配置
不用版本的 karma 配置会有所不同,这里使用 v0.13 版。
karma 的运行配置可以保存在独立的文件中,也可以在 Gruntfile.js 中直接配置,由于 karma 的配置参数较多,建议保存在独立的配置文件中。
这样,我们可以针对不同的测试场景,创建不同的 karma 配置文件,最后通过 grunt 来使用。
karma 配置文件中的 basePath 是一个非常重要的参数,用来表示 karma 配置文件中的其它相对路径所依赖的起点。
如果这是一个对象路径,就会相对于配置文件来定位。
默认是 ‘‘, 也就是说配置文件所在的目录就是相对的根目录了。
在 karma 实际运行的时候,这个目录会被自动映射到 /base 之下。
files 中配置可以通过 karma 服务器访问哪些文件,在这里列出的文件,才可以通过 HTTP 访问到。当使用对象方式表示路径的时候,included 表示 karma 是不是需要自动生成一个 script 标记来加载特定的脚本,设置为 false 表示不生成这个标记,而 served 则表示是否可以通过 karma 的网站服务器访问这个文件。
plugins 在 v0.13 中可以不同配置,karma 会自动加载位于 node_modules 中的 karma-* 的 karma 模块。
// Karma configuration // Generated on Tue Sep 15 2015 12:41:55 GMT+0800 (China Standard Time) 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 // if requirejs used, frameworks: [‘jasmine‘, ‘requirejs‘], // list of files / patterns to load in the browser files: [ // inclueded, default: true, should the files be included in the browser using <script> tag? // use false if you want to load them manually. eg using require.js // served, default: true, should the files be served by karma‘s webserver? // watched, default: true, if autowatch is true, all files that have set watched to true will be watched for change { pattern: ‘src/**/*.*‘, included: false, served: true }, { pattern: ‘test/**/*_spec.js‘, included: false, served: true }, //in version 0.13, the item should be last ‘test-main.js‘, ], // list of files to exclude exclude: [ ‘src/js/main.js‘ ], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { }, // test results reporter to use // possible values: ‘dots‘, ‘progress‘ // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: [‘mocha‘], // 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, // By default, Karma loads all sibling NPM modules which have a name starting with karma-*. }) }
在配合 requirejs 的时候,我们还需要一个 main.js 文件对 requirejs 进行配置。
在 requirejs 中也有一个 baseUrl 配置参数,这里一定要考虑刚才karma 的 basePath 进行配置,由于在 karma 的网站中,前面又添加了 /base ,所以,现在 requirejs 的 baseUrl 就会变成 /base/src/js 了。
这样,修改了 baseUrl, 我们其它的模块 Id 就不用变化了。
我们希望能够通过 karma 直接加载测试并执行测试,所以,karma 帮助我们生成了一段脚本,从我们网站中的脚本文件中筛选出测试文件,直接加载执行,脚本通过正则表达式 /(spec|test)\.js$/i 来筛选,可以看到,它直接检查脚本文件名中,以 spec.js 或者 test.js 结尾的脚本文件。找到之后,通过 requirejs 配置中的 deps 提前加载并执行。
var allTestFiles = []; var TEST_REGEXP = /(spec|test)\.js$/i; // Get a list of all the test files to include Object.keys(window.__karma__.files).forEach(function(file) { if (TEST_REGEXP.test(file)) { // Normalize paths to RequireJS module names. // If you require sub-dependencies of test files to be loaded as-is (requiring file extension) // then do not normalize the paths // var normalizedTestModule = file.replace(/^\/base\/|\.js$/g, ‘‘); var normalizedTestModule = file; allTestFiles.push(normalizedTestModule); } }); require.config({ // Karma serves files under /base, which is the basePath from your config file baseUrl: ‘/base/src/js‘, paths:{ ‘angular‘:‘lib/angular.min‘ }, shim:{ ‘angular‘:{ ‘exports‘: ‘angular‘ } }, // dynamically load all test files deps: allTestFiles, // we have to kickoff jasmine, as it is asynchronous callback: window.__karma__.start }); require([‘angular‘, ‘controllers/controllers‘], function(angular, controller){ console.log(‘main.js loaded.‘); console.log( controller); }, function(){ console.log("load error"); console.log( arguments); });
4. 测试的写法
比如,我们有一个模块的定义。
define([], function(){ return function ($scope){ $scope.name = ‘World‘; $scope.phones = [ {‘name‘: ‘Nexus S‘, ‘snippet‘: ‘Fast just got faster with Nexus S.‘, ‘age‘:1 }, {‘name‘: ‘Motorola XOOM™ with Wi-Fi‘, ‘snippet‘: ‘The Next, Next Generation tablet.‘, ‘age‘:2 }, {‘name‘: ‘MOTOROLA XOOM™‘, ‘snippet‘: ‘The Next, Next Generation tablet.‘, ‘age‘:3 } ]; $scope.orderProp = ‘age‘; }; })
测试也是一个模块,一定要在测试的依赖项中,将测试所依赖的模块加载进来。
// use define to load dependency block. define([‘controllers/controllers‘], function(target) { // regular jasmine test case describe(‘PhoneCat controllers‘, function() { describe(‘PhoneListCtrl‘, function(){ var scope, ctrl; beforeEach(function(){ scope = {}; // create controller instance ctrl = new target( scope ); }); it(‘should create "Phone" model with 3 phones.‘, function(){ expect( scope.phones.length ).toBe( 3 ); }); it(‘ the name of "Phone" model should be "World".‘, function(){ expect( scope.name ).toBe(‘World‘); }); it(‘the age of "Phone" model should be "age",‘, function(){ expect( scope.orderProp ).toBe( ‘age‘ ); }) }) }); });
执行之后的输出如下:
PS C:\study\photocat> grunt karma Running "karma:unit" (karma) task 16 09 2015 14:38:05.540:WARN [karma]: No captured browser, open http://localhost:9876/ 16 09 2015 14:38:05.555:INFO [karma]: Karma v0.13.9 server started at http://localhost:9876/ 16 09 2015 14:38:05.564:INFO [launcher]: Starting browser Chrome 16 09 2015 14:38:08.382:INFO [Chrome 44.0.2403 (Windows 7 0.0.0)]: Connected on socket IUSWkKhxqSbjN8NzAAAA with id 89877642 Start: hello, jasmine. √ say hello to jasmine. PhoneCat controllers PhoneListCtrl √ should create "Phone" model with 3 phones. √ the name of "Phone" model should be "World". √ the age of "Phone" model should be "age", Finished in 0.013 secs / 0.002 secs SUMMARY: √ 4 tests completed
5. 总结
在配合 requirejs 的时候, karma 路径,requirejs 的路径一定要注意,karma 的服务器将我们的服务内容放在了 /base 之下,我们在使用的时候,必须特别注意这一点。