使用karma和jasmine進行angularjs單元測試

JavaScript是一門腳本語言,並不包含編譯器,因此沒法保證類型安全。javascript

單元測試能夠彌補編譯器的缺少,找出潛在的缺陷。css

1 Jasmine測試框架

Jasmine是一種測試框架,定義了測試用例的語法、API、如何編寫斷言等等。相似的產品還包括Mocha等。html

call browser-visit-direct.html  # windows
open browser-visit-direct.html  # osx

或者直接用瀏覽器打開browser-visit-direct.html:java

<html>
    <head>
        <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.3/jasmine.min.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.3/jasmine.min.js"></script>
        <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.3/jasmine-html.min.js"></script>
        <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.3/boot.min.js"></script>
    </head>
    <body>
    </body>
    <script type="text/javascript">
        var calculator = {
            sum: function(x, y) {
                return x + y;
            }
        }
        // Paste in the test code here.
        describe('calculator', function () {
            it('1 + 1 should equal 2'); //沒有添加測試的主體,所以狀態上方顯示的是pending
            it('1 + 1 should equal 2', function() {
                expect(1 + 1).toBe(1);
            });
            it('1 + 1 should equal 2', function() {
                expect(1 + 1).toBe(2);
            });
            it('1 + 1 should equal 2', function () {
                expect(calculator.sum(1, 1)).toBe(2);
            });
        });
        
        it('others', function(){
          expect(true).toBe(true);
          expect(false).not.toBe(true);
          expect(1).toEqual(1);
          expect('foo').toEqual('foo');
          expect('foo').not.toEqual('bar');
        })
    </script>
</html>

能夠看到node

failures

Spec list

其中,測試中的首句describe能夠理解爲包含多個測試樣例的容器。git

it是單元測試的核心代碼,咱們在it塊內調用函數,檢查返回值是否符合預期。每一個expect語句都接受一個參數,根據內置的匹配函數或者自定義匹配函數進行判斷。angularjs

經常使用的Jasmine匹配函數包括:github

  1. toEqual
  2. toBe
  3. toBeTruthy
  4. toBeFalsy

……web

2 Karma安裝和配置

一、install nodejs: https://nodejs.org/en/ajax

二、淘寶cnpm:https://npm.taobao.org/

查看全局安裝的模塊:cnpm list -g

三、安裝karma

$ cnpm install -g karma-cli # 爲了能在命令行直接執行 karma 命令
$ mkdir ex03 & cd ex03
$ cnpm install karma --save-dev  # 自動生成package.json
$ cnpm install karma-jasmine karma-chrome-launcher --save-dev
$ cat package.json                    
{                                     
  "devDependencies": {                
    "karma": "^1.7.1",                
    "karma-chrome-launcher": "^2.2.0",
    "karma-jasmine": "^1.1.0"         
  }                                   
}     

$ karma init # 交互式自動生成karma.conf.js

3 單元測試

3.1 編寫

// File: ex03/controller.js                                           
angular.module('notesApp', [])                                            
  .controller('ListCtrl', [function() {                                   
                                                                          
    var self = this;                                                      
    self.items = [                                                        
      {id: 1, label: 'First', done: true},                                
      {id: 2, label: 'Second', done: false}                               
    ];                                                                    
                                                                          
    self.getDoneClass = function(item) {                                  
      return {                                                            
        finished: item.done,                                              
        unfinished: !item.done                                            
      };                                                                  
    };                                                                    
}]);                                                                      
// File: ex03/controllerSpec.js                                       
describe('Controller: ListCtrl', function() {                             
  // Instantiate a new version of my module before each test              
  beforeEach(module('notesApp'));                                         
                                                                          
  var ctrl;                                                               
                                                                          
  // Before each unit test, instantiate a new instance                    
  // of the controller                                                    
  beforeEach(inject(function($controller) {                               
    ctrl = $controller('ListCtrl');                                       
  }));                                                                    
                                                                          
  it('should have items available on load', function() {                  
    expect(ctrl.items).toEqual([                                          
      {id: 1, label: 'First', done: true},                                
      {id: 2, label: 'Second', done: false}                               
    ]);                                                                   
  });                                                                     
                                                                          
  it('should have highlight items based on state', function() {           
    var item = {id: 1, label: 'First', done: true};                       
                                                                          
    var actualClass = ctrl.getDoneClass(item);                            
    expect(actualClass.finished).toBeTruthy();                            
    expect(actualClass.unfinished).toBeFalsy();                           
                                                                          
    item.done = false;                                                    
    actualClass = ctrl.getDoneClass(item);                                
    expect(actualClass.finished).toBeFalsy();                             
    expect(actualClass.unfinished).toBeTruthy();                          
  });                                                                     
                                                                          
});

karma.conf.js中,files包含須要引入的js文件,browser表示要啓動的瀏覽器

// list of files / patterns to load in the browser
   files: [
      'angular.min.js',
      'angular-mocks.js',
      'controller.js',
      'controllerSpec.js'
    ],
    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera
    // - Safari (only Mac)
    // - PhantomJS
    // - IE (only Windows)
    browsers: ['Chrome'],

3.2 運行

$ karma start
11 11 2017 10:29:41.138:WARN [karma]: No captured browser, open http://localhost:8080/
11 11 2017 10:29:41.152:INFO [karma]: Karma v1.7.1 server started at http://0.0.0.0:8080/
11 11 2017 10:29:41.153:INFO [launcher]: Launching browser Chrome with unlimited concurrency
11 11 2017 10:29:41.166:INFO [launcher]: Starting browser Chrome
11 11 2017 10:29:43.146:INFO [Chrome 62.0.3202 (Windows 10 0.0.0)]: Connected on socket Yi2gR771fEmcfPqnAAAA with id 31405350
Chrome 62.0.3202 (Windows 10 0.0.0): Executed 2 of 2 SUCCESS (0.032 secs / 0.022 secs)

源碼:https://github.com/shenjiefeng/js-fortest/tree/master/angularjs/ex04-unittest/

3.3 使用PhantomJS

phantomjs是提供一個瀏覽器環境的命令行接口

$ cnpm install --save-dev karma-phantomjs-launcher

參考官網 https://www.npmjs.com/package/karma-phantomjs-launcher ,修改karma.conf.js

運行

$ karma start --browsers PhantomJS_custom
11 11 2017 11:10:29.055:WARN [karma]: No captured browser, open http://localhost:8080/
11 11 2017 11:10:29.071:INFO [karma]: Karma v1.7.1 server started at http://0.0.0.0:8080/
11 11 2017 11:10:29.071:INFO [launcher]: Launching browser PhantomJS_custom with unlimited concurrency
11 11 2017 11:10:29.086:INFO [launcher]: Starting browser PhantomJS
11 11 2017 11:10:29.133:INFO [phantomjs.launcher]: ACTION REQUIRED:
11 11 2017 11:10:29.133:INFO [phantomjs.launcher]:
11 11 2017 11:10:29.133:INFO [phantomjs.launcher]:   Launch browser at
11 11 2017 11:10:29.133:INFO [phantomjs.launcher]:   http://localhost:9000/webkit/inspector/inspector.html?page=2
11 11 2017 11:10:29.133:INFO [phantomjs.launcher]:
11 11 2017 11:10:29.133:INFO [phantomjs.launcher]: Waiting 15 seconds ...
11 11 2017 11:10:47.356:INFO [PhantomJS 2.1.1 (Windows 8 0.0.0)]: Connected on socket gYJpL9b4Ax1vcSXRAAAA with id 54603354
PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 2 of 2 SUCCESS (0 secs / 0.017 secs)

3.4 代碼覆蓋率

代碼覆蓋率經常被拿來做爲衡量測試好壞的指標

cnpm install karma karma-coverage --save-dev

參照官網 https://www.npmjs.com/package/karma-coverage 修改karma.conf.js

// coverage settings
    // coverage reporter generates the coverage
    reporters: ['progress', 'coverage'],

    preprocessors: {
      // source files, that you wanna generate coverage for
      // do not include tests or libraries
      // (these files will be instrumented by Istanbul)
      'src/**/*.js': ['coverage']
    },

    // optionally, configure the reporter
    coverageReporter: {
      type : 'html',
      dir : 'coverage/'
    },
    // end coverage settings done

運行

λ karma start                                                                           
11 11 2017 15:44:39.502:WARN [karma]: No captured browser, open http://localhost:8080/  
11 11 2017 15:44:39.502:INFO [karma]: Karma v1.7.1 server started at http://0.0.0.0:8080
/                                                                                       
11 11 2017 15:44:39.518:INFO [launcher]: Launching browsers PhantomJS, PhantomJS_custom 
with unlimited concurrency                                                              
11 11 2017 15:44:39.518:INFO [launcher]: Starting browser PhantomJS                     
11 11 2017 15:44:39.565:INFO [launcher]: Starting browser PhantomJS                     
11 11 2017 15:44:39.580:INFO [phantomjs.launcher]: ACTION REQUIRED:                     
11 11 2017 15:44:39.580:INFO [phantomjs.launcher]:                                      
11 11 2017 15:44:39.580:INFO [phantomjs.launcher]:   Launch browser at                  
11 11 2017 15:44:39.580:INFO [phantomjs.launcher]:   http://localhost:9000/webkit/inspec
tor/inspector.html?page=2                                                               
11 11 2017 15:44:39.580:INFO [phantomjs.launcher]:                                      
11 11 2017 15:44:39.580:INFO [phantomjs.launcher]: Waiting 15 seconds ...               
11 11 2017 15:44:42.665:INFO [PhantomJS 2.1.1 (Windows 8 0.0.0)]: Connected on socket tR
S1I1S_vRFKRXcwAAAA with id 93001704                                                     
11 11 2017 15:44:57.665:INFO [PhantomJS 2.1.1 (Windows 8 0.0.0)]: Connected on socket dk
GUuJAC80MqA_98AAAB with id 67337991                                                     
PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 2 of 2 SUCCESS (0.016 secs / 0.01 secs)     
PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 2 of 2 SUCCESS (0 secs / 0.012 secs)        
TOTAL: 4 SUCCESS

查看生成的代碼覆蓋率

$ ls "coverage\PhantomJS 2.1.1 (Windows 8 0.0.0)\
base.css  index.html  prettify.css  prettify.js  sort-arrow-sprite.png  sorter.js

100 code coverage

4 在測試中注入html,訪問DOM元素

4.1 將html寫入到js中

源碼:https://github.com/shenjiefeng/js-fortest/tree/master/angularjs/ex02-karma-tutorial

4.2 從外部html文件導入

將html寫入到js中太麻煩了,可不能夠用karma-fixture來實現導入呢

karma start
11 11 2017 16:12:49.337:WARN [karma]: No captured browser, open http://localhost:9876/
11 11 2017 16:12:49.353:INFO [karma]: Karma v1.7.1 server started at http://0.0.0.0:9876/
11 11 2017 16:12:49.353:INFO [launcher]: Launching browsers PhantomJS, PhantomJS_custom with unlimited concurrency
11 11 2017 16:12:49.368:INFO [launcher]: Starting browser PhantomJS
11 11 2017 16:12:49.399:INFO [launcher]: Starting browser PhantomJS
11 11 2017 16:12:49.415:INFO [phantomjs.launcher]: ACTION REQUIRED:
11 11 2017 16:12:49.415:INFO [phantomjs.launcher]:
11 11 2017 16:12:49.415:INFO [phantomjs.launcher]:   Launch browser at
11 11 2017 16:12:49.415:INFO [phantomjs.launcher]:   http://localhost:9000/webkit/inspector/inspector.html?page=2
11 11 2017 16:12:49.415:INFO [phantomjs.launcher]:
11 11 2017 16:12:49.415:INFO [phantomjs.launcher]: Waiting 15 seconds ...
11 11 2017 16:12:52.545:INFO [PhantomJS 2.1.1 (Windows 8 0.0.0)]: Connected on socket ddCmyvggaUdxkr1rAAAA with id 6133081
11 11 2017 16:13:07.557:INFO [PhantomJS 2.1.1 (Windows 8 0.0.0)]: Connected on socket C670QlbDCLLzPfHYAAAB with id 60711068
LOG: Fixture{base: 'test', id: 'fixture_container', json: [], scriptTypes: Object{application/ecmascript: 1, application/javascript: 1, application/x-ecmascript: 1, application/x-javascript: 1, text/ecmascript: 1, text/javascript: 1, text/javascript1.0: 1, text/javascript1.1: 1, text/javascript1.2: 1, text/javascript1.3: 1, text/javascript1.4: 1, text/javascript1.5: 1, text/jscript: 1, text/livescript: 1, text/x-ecmascript: 1, text/x-javascript: 1}, el: <div id="fixture_container"></div>}
PhantomJS 2.1.1 (Windows 8 0.0.0) Calculator should return 3 for 1 + 2 FAILED
        ReferenceError: Cannot find fixture 'test/hello.html' in node_modules/_karma-fixture@0.2.6@karma-fixture/lib/fixture.js (line 141)
        _throwNoFixture@node_modules/_karma-fixture@0.2.6@karma-fixture/lib/fixture.js:141:77
        load@node_modules/_karma-fixture@0.2.6@karma-fixture/lib/fixture.js:78:33
        test/calculator.test.js:49:24
        executeFiltered@node_modules/_karma-jasmine@0.2.3@karma-jasmine/lib/boot.js:126:18
        node_modules/_karma-jasmine@0.2.3@karma-jasmine/lib/adapter.js:171:31
        loaded@http://localhost:9876/context.js:162:17
        global code@http://localhost:9876/context.html:48:28
        TypeError: null is not an object (evaluating 'document.getElementById('x').value = 1') in test/calculator.test.js (line 60)
        test/calculator.test.js:60:33
        executeFiltered@node_modules/_karma-jasmine@0.2.3@karma-jasmine/lib/boot.js:126:18
        node_modules/_karma-jasmine@0.2.3@karma-jasmine/lib/adapter.js:171:31
        loaded@http://localhost:9876/context.js:162:17
        global code@http://localhost:9876/context.html:48:28

對應的代碼覆蓋率:

error code coverage

error code coverage
能夠看出,9句中4句未覆蓋。

如何解決ReferenceError: Cannot find fixture 'test/hello.html'問題呢?

幾經嘗試,仍是一樣的問題,憂桑啊。

參考

  1. 《AngularJS即學即用》
  2. https://npmjs.org/browse/keyword/karma-launcher
  3. https://segmentfault.com/a/1190000005708178
  4. Karma Tutorial - Unit Testing JavaScript
相關文章
相關標籤/搜索