開発者ブログ
HTML5資料室 » QUnit, Karma, JSHint, JSDoc3 を組み合わせて Jenkins 上で CI を行う

QUnit, Karma, JSHint, JSDoc3 を組み合わせて Jenkins 上で CI を行う

Last modified by mitsubuc on 2015/06/26, 15:40

QUnit, Karma, JSHint, JSDoc3 を組み合わせて Jenkins 上で CI を行う

調査時期
2015 年 6 月

概要

以下の3つを Jenkins を利用して継続的に実行する方法を紹介します。

  • JSHint によってソースコード検査を行う
  • JSDoc3 によってAPIドキュメントを生成する
  • QUnit で書いた単体テストを Karma 上で実行し、カバレッジをとる

前提条件

  • プロジェクトがあるマシンと Jenkins があるマシンに Node.js がインストール済みであること

プロジェクト側に必要な設定

プロジェクト側で必要となる設定を紹介します。

各種ツールを利用するための package.json を用意しツールをインストールする

以下のような package.json をプロジェクトのルートディレクトリに用意します。

{
   "private": true
}

プロジェクトのルートディレクトリ上で必要なモジュールを以下のコマンドでインストールしていきます。
※インストールしたモジュールはルートディレクトリの node_modules 以下に入り、package.json も更新されます

  > npm install --save-dev {モジュール名}  

必要なモジュール一覧

JSHint の設定ファイルを用意する

JSLint/JSHint の解説JSHint 公式のドキュメント を参考に JSHint の設定記述した .jshintrc ファイルをプロジェクトのルートディレクトリに作成します。

設定のサンプル

{
   "bitwise": true,
   "camelcase": true,
   "curly": true,
   "eqeqeq": true,
   "es3": false,
   "forin": true,
   "freeze": true,
   "immed": true,
   "latedef": true,
   "newcap": true,
   "noarg": true,
   "noempty": true,
   "nonbsp": true,
   "nonew": true,
   "plusplus": false,
   "quotmark": "single",
   "undef": true,
   "unused": true,
   "strict": true,

   "maxparams": 6,
   "maxdepth": 2,
   "maxstatements": 1000,
   "maxcomplexity": 15,
   "maxlen": 10000,

   "eqnull": true
}

JSDoc の設定ファイルを用意する

以下のようにJSDoc の設定を記述した jsdoc-conf.json ファイルをプロジェクトのルートディレクトリに作成します。

{
   "opts": {
       "encoding": "utf8",
       "recurse": false,
       "private": false,
       "lenient": true
    }
}

ビルド用の定数を定義したファイルを用意する

以下の定数を定義する build-properties.js ファイルをプロジェクトのルートディレクトリに作成します。
※ gulp 設定ファイルと karma 設定ファイルで参照する定数をまとめて記述するためです

  • BUILD_DIR: ビルドディレクトリのパス
  • SRC_PATTERN: ソースファイルが置いてあるパスのパターン
  • JSHINT_TARGET_FILES: JSHint のターゲットとなるファイルパターンのリスト
  • JSHINT_REPORT_PATH: JSHINT_REPORT_DIR: JSHint のレポートを出力するディレクトリ
  • JSDOC_TARGET_FILES: JSHint のレポートを出力するパス
  • JSDOC_OUTPUT_DIR: JSDoc のターゲットとなるファイルパターンのリスト
  • JSDoc を出力するディレクトリのパス
  • KARMA_TARGET_FILES: Karma が読み込むファイルパターンのリスト
  • TEST_REPORT_PATH: テストの結果を出力するディレクトリのパス
  • COVERAGE_REPORT_DIR: カバレッジのレポートを出力するディレクトリのパス

KARMA_TARGET_FILES に関する注意点

  • リストの順に読み込むので依存関係に従った順で記述する
  • CSS なども必要であれば追加しておくと読み込まれる
  • debug ボタン押下時に QUnit の UI を出すために以下の JavaScript ファイルを作成し、QUnit とテストコードの間に読み込ませる
(function() {
   /* jshint browser: true */
   'use strict';

   // qunit 要素を追加
   var qunitNode = document.createElement('div');
    qunitNode.setAttribute('id', 'qunit');
   document.body.appendChild(qunitNode);

   // fixture 要素を追加
   var fixtureNode = document.createElement('div');
    fixtureNode.setAttribute('id', 'qunit-fixture');
   document.body.appendChild(fixtureNode);
})();

Karma の設定ファイルを用意する

以下のような Karma 設定を記述した karma.conf.js ファイルをプロジェクトのルートディレクトリに作成します。

/* jshint node: true */
'use strict';

var prop = require('./build-properties');

module.exports = function(config) {
   var options = {
        frameworks: ['qunit'],
        files: prop.KARMA_TARGET_FILES,
        excludes: [],
        reporters: ['dots', 'junit'],
        port: 9876,
        colors: true,
        logLevel: config.LOG_DEBUG,
        client: {
            captureConsole: false
        },
        autoWatch: true,
        browsers: ['Chrome', 'IE'],
        singleRun: false,
        junitReporter: {
            outputFile: prop.TEST_REPORT_PATH,
            suite: ''
        }
    };

    config.set(options);
};

また、カバレッジ測定用の設定を記述した karma-coverage.conf.js ファイルをプロジェクトのルートディレクトリに作成します。

/* jshint node: true */
'use strict';

var karmaConf = require('./karma.conf');
var prop = require('./build-properties');


function get(obj, property, defaultValue) {
   if (obj[property] == null) {
        obj[property] = defaultValue;
    }
   return obj[property];
}

function makeSubdir(path) {
   return function(browser) {
       return browser + '/' + path;
    };
}

module.exports = function(config) {
    karmaConf(config);

   var preprocessors = get(config, 'preprocessors', {});
   var preprocessorTarget = get(preprocessors, prop.SRC_PATTERN, []);
    preprocessorTarget.push('coverage');

   var reporters = get(config, 'reporters', []);
    reporters.push('coverage');

    config.coverageReporter = {
        dir: prop.COVERAGE_REPORT_DIR,
        reporters: [
            { type: 'html', subdir: makeSubdir('report') },
            { type: 'cobertura', subdir: makeSubdir('xml') },
            { type: 'text-summary' }
        ]
    };

    config.singleRun = true;
};

ビルドを実行する Gulpfile.js を用意する

以下のような Gulp 設定を記述した gulpfile.js ファイルをプロジェクトのルートディレクトリに作成します。

/* jshint node: true */
'use strict';

// ---- 依存モジュールのロード ---- //

// -- Node.js 標準モジュール -- //
var os = require('os');
var spawn = require('child_process').spawn;
var path = require('path');

// -- Gulp 関連モジュール -- //
var gulp = require('gulp');
var plugins = require('gulp-load-plugins')();
var log = plugins.util.log;
var colors = plugins.util.colors;

// -- ディレクトリ操作モジュール -- //
var del = require('del');
var mkdirp = require('mkdirp');

// -- Karma モジュール -- //
var karma = require('karma');


// ---- ビルド用定数定義 ---- //

var prop = require('./build-properties');


// ---- 実行環境の判定 ---- //

var isWindows = !!os.type().match(/^Windows/);


// ---- NPM コマンド実行関数定義 ---- //

function npmCommand(command, args, cb) {
   var actualCommand = command;
   var actualArgs = args;

   if (isWindows) {
        actualCommand = 'cmd.exe';
        actualArgs = ['/c', '.\\node_modules\\.bin\\' + command].concat(args);
    }

    log(colors.blue('Run: ') + command + ' ' + args.join(' '));

   var childProcess = spawn(actualCommand, actualArgs, {
        stdio: ['ignore', 1, 2]
    });

    childProcess.on('error', function(e) {
        log(colors.red('Fail NPM Command: ' + command));
        log(e);

       throw e;
    });

    childProcess.on('exit', function(exitCode) {
       if (exitCode === 0) {
            cb();
           return;
        }

        log(colors.red('Fail NPM Command: ' + command));
        log('exit code: ' + exitCode);

       throw new Error('exit code: ' + exitCode);
    });
}


// ---- Karma 実行関数 ---- //

function startKarma(configFile, cb) {
    karma.server.start({
        configFile: path.resolve(configFile)
    }, function(exitCode) {
        log(colors.green('Karma Exit: ' + exitCode));
        cb();
    });
}


// ---- タスクの定義 ---- //

gulp.task('clean', function(cb) {
    del(prop.BUILD_DIR, cb);
});

gulp.task('jshint', function() {
    mkdirp.sync(prop.JSHINT_REPORT_DIR);

   return gulp.src(prop.JSHINT_TARGET_FILES)
        .pipe(plugins.jshint('.jshintrc'))
        .pipe(plugins.jshint.reporter('jshint-stylish'))
        .pipe(plugins.jshint.reporter(plugins.jshintXmlFileReporter))
        .on('end', plugins.jshintXmlFileReporter.writeFile({
            format: 'checkstyle',
            filePath: prop.JSHINT_REPORT_PATH,
            alwaysReport: true
        }));
});

gulp.task('jsdoc', function() {
    mkdirp.sync(prop.JSDOC_OUTPUT_DIR);

   return gulp.src(prop.JSDOC_TARGET_FILES)
        .pipe(plugins.util.buffer(function(err, files) {
           var args = ['-c', 'jsdoc-conf.json', '-d', prop.JSDOC_OUTPUT_DIR];

           var paths = files.map(function(file) {
               return file.path;
            });

            npmCommand('jsdoc', args.concat(paths), function() {
                log(colors.green('SUCCESS: jsdoc'));
            });
        }));
});

gulp.task('karma', function(cb) {
    startKarma('karma.conf.js', cb);
});

gulp.task('coverage', function(cb) {
    startKarma('karma-coverage.conf.js', cb);
});

gulp.task('default', ['jshint', 'jsdoc', 'coverage']);

Gulp の実行方法

プロジェクトのルートディレクトリで以下のようなコマンドを実行することで Gulp タスクを実行できます。

 > .\node_modules\.bin\gulp {タスク名}

これまでの設定に従っていれば以下のタスクを実行することができます。

  • clean: ビルドディレクトリを削除します
  • jshint: JSHint を実行し結果をコンソールと checkstyle 形式の XML で出力します
  • jsdoc: JSDoc を実行し API ドキュメントを出力します
  • coverage: Karma を一回実行しテスト結果とカバレッジのレポートを出力します
  • karma: Karma を立ち上げ、ファイルが更新されるたびにテストを走らせます
  • default(引数なしでも可): jshint, jsdoc, coverage を実行します

Jenkins に必要な設定

Jenkins 上では以下のコマンドを実行するジョブを作成します。

  • npm prune を実行する
  • npm install を実行する
  • Gulp の clean タスクを実行する
  • Gulp の default タスクを実行する

ジョブを実行後、JSHint の結果は checkstyle 形式の XML で、テストの結果は JUnit 形式の XML で、カバレッジの結果は cobertura 形式の XML で読み込むことができます。

Karma を Selenium Grid のブラウザからテストしたい場合に必要な設定

Karma を開発者のPC上にあるブラウザではなく Selenium Grid 上のブラウザから実行する場合に必要な設定です。
※ Selenium Grid に関しての説明は こちら を参照してください

以下のモジュールを追加でインストールします。

karma.conf.js の設定に以下のように customLaunchers を追加します。

        customLaunchers: {
           'selenium-grid-IE': {
                base: 'SeleniumWebdriver',
                browserName: 'internet explorer',
                getDriver: function() {
                   var builder = new require('selenium-webdriver').Builder();
                   return builder
                        .forBrowser('internet explorer')
                        .usingServer('http://localhost:4444/wd/hub') // Selenium Grid の Hub の URL を指定します
                       .build();
                }
            }
        },

        browsers: ['selenium-grid-IE'], // browsers: ['Chrome', 'IE'] から customLaunchers で追加したものに差し替える

上記設定後に Selenium Grid の Hub と Node を立ち上げて karma を実行すると Selenium Grid 上のブラウザでテストが実行されます。


Copyright (C) 2012-2017 NS Solutions Corporation, All Rights Reserved.