CLOVER🍀

That was when it all began.

テストランナーKarmaを試す

最近、ちょっと気になっていたテストランナー、Karmaを試してみます。

Karma - Spectacular Test Runner for Javascript

Karmaを使うと、実際のブラウザやPhantomJSでテストができたり、ファイルに変更があるとKarmaが検知してくれて自動実行してくれたりするみたいです。

PhantomJSと合わせられるところなどが良さそうだなぁと思い、試してみることにしました。

インストール用ドキュメントや

Karma - Installation

設定ドキュメントを参考にしながた使ってみたいと思います。

Karma - Configuration

その他、このあたりも参考に。

イマドキのJSテスト - karma + karma-html2js-preprocessorでブラウザ/DOMを絡めたUIテストを実行する編 〜 JSおくのほそ道 #031

小さくKarmaとmochaでテスト環境を整える。 | niwaringo() {Tumblr}

karma+mocha+power-assertでDOM操作を含むユニットテストをES6で書く | WebDesign Dackel

環境としては、

  • Karma
  • Babel(ES2015)
  • Browserify
  • Mocha+Chai
  • Google Chorme

を組み合わせて使うことにしたいと思います。

Karmaだけでテストが実行できるわけではないので、実際のテストコードとしてはMocha、Chaiなどが別途必用になります。今回は、この2つを利用しました。

準備

まずは、必要なnpmモジュールをインストールします。

## Babel
$ npm install --save-dev babelify babel-preset-es2015
$ npm install --save babel-polyfill

## Browserify
$ npm install --save-dev browserify watchify uglify-js

## Mocha, Chai
$ npm install --save-dev mocha chai

## Karma
$ npm install --save-dev karma karma-browserify karma-chai karma-mocha

## Karma+Chrome
$ npm install --save-dev karma-chrome-launcher

「karma-〜」と付いているのは、だいたいは他のツールなどとの連携用です。また、Chromeと組み合わせるので「karma-chrome-launcher」もインストールします。

KarmaとBrowserifyを組み合わせてかつ変更を監視する場合は、Watchifyが必須みたいです。UglifyJs2は、テーマとは直接関係ありませんが、Minify用…。

ES2015を使うために、.babelrcも作成。

$ echo '{ "presets": ["es2015"] }' > .babelrc

Karmaの設定ファイルを作る

Karmaを使うには、設定ファイルを作成する必要があるようです。「karma init」で作成します。

$ ./node_modules/karma/bin/karma init

対話形式で進んでいくと、karma.conf.jsというファイルができます。

こちらをさらに編集し、今回はこういう内容にしました。
karma.conf.js

// Karma configuration
// Generated on Sat May 07 2016 17:27:20 GMT+0900 (JST)

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: ['browserify', 'mocha', 'chai'],

    browserify: {
      debug: true,
      transform: ['babelify']
    },


    // list of files / patterns to load in the browser
    files: [
      'test/**/*.js'
    ],


    // list of files to exclude
    exclude: [
    ],


    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
        "src/**/*.js": ["browserify"],
        "test/**/*.js": ["browserify"]
    },


    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],


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

利用するフレームワークの設定として、browserify、mocha、chaiを追加。

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['browserify', 'mocha', 'chai'],

Browserifyの設定としては、Babelを利用するようにします。

    browserify: {
      debug: true,
      transform: ['babelify']
    },

ソースとテストコードにかかるPre-Processorにも、Browserifyを設定。

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
        "src/**/*.js": ["browserify"],
        "test/**/*.js": ["browserify"]
    },

テスト対象のコードとテストコードを作る

それでは、テスト対象のコードとテストコードを作っていきます。

まずは、ディレクトリ作成(distはオマケです)。

$ mkdir src test dist

ブラウザ上で動くということなので、documentなどを使った適当なコードを書いてみます。
src/hello-karma.js

export function getElementClasses(element) {
    let classes = element.className.split(" ");
    return classes;
};

export function getText(element) {
    return element.textContent;
};

export function createTextNode(text) {
    return document.createTextNode(text);
};


テストコード。
test/hello-karma-test.js

const should = require("chai").should();

describe("Karma Getting Started", () => {
    it("getElementClasses:success", () => {
        const helloKarma = require("../src/hello-karma");

        let span = document.createElement("span");
        span.setAttribute("class", "foo bar");
        span.appendChild(document.createTextNode("Hello World"));

        helloKarma.getElementClasses(span).should.deep.equal(["foo", "bar"]);
    });

    it("getText:success", () => {
        const helloKarma = require("../src/hello-karma");

        let span = document.createElement("span");
        span.setAttribute("class", "foo bar");
        span.appendChild(document.createTextNode("Hello World"));

        helloKarma.getText(span).should.equal("Hello World");
    });

    it("createText:success", () => {
        const helloKarma = require("../src/hello-karma");

        let span = document.createElement("span");
        span.setAttribute("class", "foo bar");
        span.appendChild(helloKarma.createTextNode("Hello World"));

        helloKarma.getText(span).should.equal("Hello World");
    });

    it("getElementClasses:fail", () => {
        const helloKarma = require("../src/hello-karma");

        let span = document.createElement("span");
        span.setAttribute("class", "foo bar");
        span.appendChild(document.createTextNode("Hello World"));

        helloKarma.getElementClasses(span).should.deep.equal(["foo", "fuga"]);
    });

    it("getText:fail", () => {
        const helloKarma = require("../src/hello-karma");

        let span = document.createElement("span");
        span.setAttribute("class", "foo bar");
        span.appendChild(document.createTextNode("Hello World"));

        helloKarma.getText(span).should.equal("Hello Karma");
    });

    it("createText:fail", () => {
        const helloKarma = require("../src/hello-karma");

        let span = document.createElement("span");
        span.setAttribute("class", "foo bar");
        span.appendChild(helloKarma.createTextNode("Hello World"));

        helloKarma.getText(span).should.equal("Hello Karma");
    });
});

成功するケースと失敗するケースを、それぞれ3つずつ用意してみました。

実行

package.jsonに、以下の用に記述してnpm runから起動してみます。

  "scripts": {
    "test:karma": "karma start karma.conf.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

「test:karma」で起動するようにしました。

実行。

$ npm run test:karma

すると、Chromeが起動してテストが実行されます。

コンソールには、このように。

07 05 2016 23:36:14.892:WARN [karma]: No captured browser, open http://localhost:9876/
07 05 2016 23:36:14.911:INFO [karma]: Karma v0.13.22 server started at http://localhost:9876/
07 05 2016 23:36:14.929:INFO [launcher]: Starting browser Chrome
07 05 2016 23:36:18.653:INFO [Chrome 50.0.2661 (Linux 0.0.0)]: Connected on socket /#7gqSuSXi-99mmrMaAAAA with id 72312583
Chrome 50.0.2661 (Linux 0.0.0) Karma Getting Started getElementClasses:fail FAILED
	AssertionError: expected [ 'foo', 'bar' ] to deeply equal [ 'foo', 'fuga' ]
	    at Assertion.assertEqual (/tmp/17ea1dc18d2f3459fee06c8c96b71486.browserify:2715:19 <- node_modules/chai/lib/chai/core/assertions.js:485:0)
	    at Assertion.ctx.(anonymous function) [as equal] (/tmp/17ea1dc18d2f3459fee06c8c96b71486.browserify:6133:25 <- node_modules/chai/lib/chai/utils/addMethod.js:41:0)
	    at Context.<anonymous> (/tmp/17ea1dc18d2f3459fee06c8c96b71486.browserify:8126:56 <- test/hello-karma-test.js:41:55)
Chrome 50.0.2661 (Linux 0.0.0) Karma Getting Started getText:fail FAILED
	AssertionError: expected 'Hello World' to equal 'Hello Karma'
	    at Context.<anonymous> (/tmp/17ea1dc18d2f3459fee06c8c96b71486.browserify:8136:41 <- test/hello-karma-test.js:51:40)
Chrome 50.0.2661 (Linux 0.0.0) Karma Getting Started createText:fail FAILED
	AssertionError: expected 'Hello World' to equal 'Hello Karma'
	    at Context.<anonymous> (/tmp/17ea1dc18d2f3459fee06c8c96b71486.browserify:8146:41 <- test/hello-karma-test.js:61:40)
Chrome 50.0.2661 (Linux 0.0.0): Executed 6 of 6 (3 FAILED) (0.228 secs / 0.027 secs)

半分失敗するテストを書いているので、3つ失敗した、と言われていますね。

この後、ソースコードやテストコードを修正したりすると、Karmaが変更を検知して再度テストを実行してくれます。

とりあえず、基礎的な使い方はわかった感じでしょうか。

オマケ

UglifyJs2を依存関係に加えていましたが、一緒にJavaScriptをビルドする設定も書いておきます。

今回は、単純にnpm runで行うようにしました。

package.jsonに、以下のように記述。
※main.jsは、別途用意したエントリポイントです

  "scripts": {
    "build": "browserify src/main.js -t [babelify] -o dist/app.js && cd dist && uglifyjs --compress --source-map app.js.map --source-map-url ./app.js.map --output app.js app.js",
    ...
  },

「npm run build」で、Browserifyでまとめ、UglifyJs2でminifyしつつ、Sourcemapを作成します。

できあがったコードは、「dist/app.js」として、Sourcemapは「dist/app.js.map」として出力されます。