學習Karma+Jasmine+istanbul+webpack自動化單元測試

龍恩0707發表於2018-02-08

學習Karma+Jasmine+istanbul+webpack自動化單元測試

1-1. 什麼是karma?
  Karma 是一個基於Node.js的Javascript測試執行過程管理工具。該工具可用於測試所有主流web瀏覽器,也可整合到CI工具,也可以
和其他程式碼編輯器一起使用,它可以監聽檔案的變化,然後自動執行。

1-2. 什麼是Jasmine?
Jasmine也是一款javascript測試框架。Jasmine官網文件地址(https://jasmine.github.io/2.3/introduction.html)

1-3. 什麼是istanbul?
istanbul 是一個單元測試程式碼覆蓋率檢查工具,它可以直觀的告訴我們,單元測試對程式碼的控制程度。

2. 安裝Karma環境
如下命令:

npm install -g karma

為了方便搭建karma環境,我們可以全域性安裝karma-cli來幫我們初始化測試環境。
如下命令安裝:

npm install -g karma-cli

我們在專案的根目錄下也需要安裝一下,如下命令:

npm install karma --save-dev

如上,安裝完成以後,我們在專案的根目錄下的命令列輸入命令:karma start
執行如下:

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, 如下圖所示:

如果出現以上資訊,表示karma已經安裝成功了。

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);
  });
});

如上測試程式碼就全部覆蓋到了。

檢視github上的demo

相關文章