Grunt - Karma 單元測試

 Karma 是 Goolge 開源的一個 Test runner, 能夠配合 Grunt 使用。javascript

1. 相關插件介紹

1.1 Karma 的官網

http://karma-runner.github.io/html

官網中的文檔其實分多鐘版本,不一樣版本的 karma 使用也有所不一樣,注意頁面右上角的版本信息。java

1.2 配合 Grunt 的插件 grunt-karma

https://github.com/karma-runner/grunt-karmanode

1.3 karma 用來啓動 chrome 的 karma 插件

https://github.com/karma-runner/karma-chrome-launchergit

1.4 jasmine 單元測試插件

https://github.com/karma-runner/karma-jasminees6

1.5 單元測試的報表格式

單元測試默認提供了兩種輸出:'dots', 'progress' ,都比較簡單,但願看到測試的詳細輸出,可使用 reporter 插件,這裏是其中之一:mochagithub

https://github.com/litixsoft/karma-mocha-reporterweb

1.6 angularJS 插件

https://github.com/karma-runner/karma-ng-scenario正則表達式

1.7 requirejs 模塊化插件

https://github.com/karma-runner/karma-requirejschrome

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>

 安裝 Karma

PS C:\study\protocat> npm install karma --save-dev
npm WARN package.json app@1.0.0 No description
npm WARN package.json app@1.0.0 No repository field.
npm WARN package.json app@1.0.0 No README data
npm WARN optional dep failed, continuing fsevents@1.0.0
|


> bufferutil@1.2.1 install C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\engine.io\node_modules\ws\node_modules\bufferutil
> node-gyp rebuild


C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\engine.io\node_modules\ws\node_modules\bufferutil>if not defined npm_config_node_gyp (node "C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bin\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild )  else (node  rebuild )
Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
  bufferutil.cc
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\limits(214): warning C4577: 'noexcept' used with no exception handling mode specified; termination on exception is not guaranteed. Specify /EHsc [C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\engine.io\node_modules\ws\node_modules\bufferutil\build\bufferutil.vcxproj]
     Creating library C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\engine.io\node_modules\ws\node_modules\bufferutil\build\Release\bufferutil.lib and object C:\study\protocat\node_modules\karma\node_modules\s
  ocket.io\node_modules\engine.io\node_modules\ws\node_modules\bufferutil\build\Release\bufferutil.expGenerating code
  Finished generating code
  bufferutil.vcxproj -> C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\engine.io\node_modules\ws\node_modules\bufferutil\build\Release\\bufferutil.node
|


> utf-8-validate@1.2.1 install C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\engine.io\node_modules\ws\node_modules\utf-8-validate
> node-gyp rebuild


C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\engine.io\node_modules\ws\node_modules\utf-8-validate>if not defined npm_config_node_gyp (node "C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bin\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild )  else (node  rebuild )
Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
  validation.cc
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\limits(214): warning C4577: 'noexcept' used with no exception handling mode specified; termination on exception is not guaranteed. Specify /EHsc [C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\engine.io\node_modules\ws\node_modules\utf-8-validate\build\validation.vcxproj]
     Creating library C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\engine.io\node_modules\ws\node_modules\utf-8-validate\build\Release\validation.lib and object C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\engine.io\node_modules\ws\node_modules\utf-8-validate\build\Release\validation.expGenerating code
  Finished generating code
  validation.vcxproj -> C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\engine.io\node_modules\ws\node_modules\utf-8-validate\build\Release\\validation.node
/


> utf-8-validate@1.2.1 install C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\socket.io-client\node_modules\engine.io-client\node_modules\ws\node_modules\utf-8-validate
> node-gyp rebuild


C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\socket.io-client\node_modules\engine.io-client\node_modules\ws\node_modules\utf-8-validate>if not defined npm_config_node_gyp (node "C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bin\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild )  else (node  rebuild )
Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
  validation.cc
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\limits(214): warning C4577: 'noexcept' used with no exception handling mode specified; termination on exception is not guaranteed. Specify /EHsc [C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\socket.io-client\node_modules\engine.io-client\node_modules\ws\node_modules\utf-8-validate\build\validation.vcxproj]
     Creating library C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\socket.io-client\node_modules\engine.io-client\node_modules\ws\node_modules\utf-8-validate\build\Release\validation.lib and object C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\socket.io-client\node_modules\engine.io-client\node_modules\ws\node_modules\utf-8-validate\build\Release\validation.expGenerating code
  Finished generating code
  validation.vcxproj -> C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\socket.io-client\node_modules\engine.io-client\node_modules\ws\node_modules\utf-8-validate\build\Release\\validation.node
|


> bufferutil@1.2.1 install C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\socket.io-client\node_modules\engine.io-client\node_modules\ws\node_modules\bufferutil
> node-gyp rebuild

/
C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\socket.io-client\node_modules\engine.io-client\node_modules\ws\node_modules\bufferutil>if not defined npm_config_node_gyp (node "C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bin\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild )  else (node  rebuild )
Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
  bufferutil.cc
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\limits(214): warning C4577: 'noexcept' used with no exception handling mode specified; termination on exception is not guaranteed. Specify /EHsc [C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\socket.io-client\node_modules\engine.io-client\node_modules\ws\node_modules\bufferutil\build\bufferutil.vcxproj]
     Creating library C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\socket.io-client\node_modules\engine.io-client\node_modules\ws\node_modules\bufferutil\build\Release\bufferutil.lib and object C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\socket.io-client\node_modules\engine.io-client\node_modules\ws\node_modules\bufferutil\build\Release\bufferutil.exp
  Generating code
  Finished generating code
  bufferutil.vcxproj -> C:\study\protocat\node_modules\karma\node_modules\socket.io\node_modules\socket.io-client\node_modules\engine.io-client\node_modules\ws\node_modules\bufferutil\build\Release\\bufferutil.node
karma@0.13.10 node_modules\karma
├── di@0.0.1
├── rimraf@2.4.3
├── graceful-fs@4.1.2
├── mime@1.3.4
├── colors@1.1.2
├── bluebird@2.10.1
├── http-proxy@1.11.2 (eventemitter3@1.1.1, requires-port@0.0.1)
├── source-map@0.4.4 (amdefine@1.0.0)
├── useragent@2.1.7 (lru-cache@2.2.4)
├── optimist@0.6.1 (wordwrap@0.0.3, minimist@0.0.10)
├── dom-serialize@2.2.0 (void-elements@1.0.0, custom-event@1.0.0, ent@2.2.0, extend@2.0.1)
├── minimatch@2.0.10 (brace-expansion@1.1.0)
├── glob@5.0.14 (path-is-absolute@1.0.0, inherits@2.0.1, inflight@1.0.4, once@1.3.2)
├── lodash@3.10.1
├── expand-braces@0.1.1 (array-uniq@1.0.2, array-slice@0.2.3, braces@0.1.5)
├── connect@3.4.0 (utils-merge@1.0.0, parseurl@1.3.0, finalhandler@0.4.0, debug@2.2.0)
├── log4js@0.6.27 (async@0.2.10, underscore@1.8.2, semver@4.3.6, readable-stream@1.0.33)
├── core-js@1.1.4
├── memoizee@0.3.9 (next-tick@0.2.2, lru-queue@0.1.0, timers-ext@0.1.0, d@0.1.1, event-emitter@0.3.3, es6-weak-map@0.1.4
, es5-ext@0.10.7)
├── body-parser@1.14.0 (bytes@2.1.0, content-type@1.0.1, depd@1.1.0, on-finished@2.3.0, raw-body@2.1.3, debug@2.2.0, ico
nv-lite@0.4.11, qs@5.1.0, http-errors@1.3.1, type-is@1.6.8)
├── chokidar@1.1.0 (arrify@1.0.0, path-is-absolute@1.0.0, is-glob@2.0.0, async-each@0.1.6, glob-parent@2.0.0, is-binary-
path@1.0.1, readdirp@2.0.0, anymatch@1.3.0)
└── socket.io@1.3.7 (has-binary-data@0.1.3, debug@2.1.0, socket.io-parser@2.2.4, socket.io-adapter@0.3.1, engine.io@1.5.
4, socket.io-client@1.3.7)

 

安裝 grunt-karma 插件。

PS C:\study\demo> npm install grunt-karma --save-dev
npm WARN package.json app@1.0.0 No description
npm WARN package.json app@1.0.0 No repository field.
npm WARN package.json app@1.0.0 No README data
grunt-karma@0.12.1 node_modules\grunt-karma
└── lodash@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 demo@1.0.0 No description
npm WARN package.json demo@1.0.0 No repository field.
npm WARN package.json demo@1.0.0 No README data
karma-chrome-launcher@0.2.0 node_modules\karma-chrome-launcher
├── fs-access@1.0.0 (null-check@1.0.0)
└── which@1.1.2 (is-absolute@0.1.7)

 

單元測試 jasmine.

PS C:\study\demo> npm install --save-dev karma-jasmine
npm WARN package.json demo@1.0.0 No description
npm WARN package.json demo@1.0.0 No repository field.
npm WARN package.json demo@1.0.0 No README data
npm WARN peerDependencies The peer dependency jasmine-core@* 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-core@2.3.4 node_modules\jasmine-core

karma-jasmine@0.3.6 node_modules\karma-jasmine

 

單元測試的輸出報表格式 mocha.

PS C:\study\demo> npm install --save-dev karma-mocha-reporter
npm WARN package.json demo@1.0.0 No description
npm WARN package.json demo@1.0.0 No repository field.
npm WARN package.json demo@1.0.0 No README data
karma-mocha-reporter@1.1.1 node_modules\karma-mocha-reporter
└── chalk@1.1.0 (escape-string-regexp@1.0.3, supports-color@2.0.0, ansi-styles@2.1.0, has-ansi@2.0.0, strip-ansi@3.0.0)

 

angular 插件

PS C:\study\demo> npm install --save-dev karma-ng-scenario
npm WARN package.json demo@1.0.0 No description
npm WARN package.json demo@1.0.0 No repository field.
npm WARN package.json demo@1.0.0 No README data
karma-ng-scenario@0.1.0 node_modules\karma-ng-scenario

 

requirejs 插件。

PS C:\study\demo> npm install --save-dev karma-requirejs
npm WARN package.json demo@1.0.0 No description
npm WARN package.json demo@1.0.0 No repository field.
npm WARN package.json demo@1.0.0 No README data
npm WARN peerDependencies The peer dependency requirejs@~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.
requirejs@2.1.20 node_modules\requirejs

karma-requirejs@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 之下,咱們在使用的時候,必須特別注意這一點。

另外,文件名稱的大小寫問題,在 Windows 系統下大小寫不敏感,可是,在 Linux 服務器上,大小寫不一樣將是不一樣的文件。注意 Karma 服務器中,即便在 Windwos 系統下,也會區分大小寫。

在配合 requirejs 時,關鍵是必定要經過 requirejs 來加載你的模塊,而不是直接嵌入帶頁面中。 

http://requirejs.org/docs/errors.html#mismatch

http://icodeit.org/2013/10/using-karma-as-the-javascript-test-runner/ 

相關文章
相關標籤/搜索