CREATIVE X 第2弾
CREATIVE X 第2弾
2015.10.06

Karma + PhantomJS + Mocha + power-assertでクライアントサイドJSのユニットテスト環境を構築してみた

いなば

こんにちは、フロントエンドエンジニアのいなばです。
今回はJSのユニットテスト環境を試行錯誤しながら構築したので、公開してみたいと思います。「クライアントサイドでのJSのユニットテストを書きたいけど環境構築がよくわからない!」という方や、「面倒臭いからテスト書かない!」という方の参考になれば幸いです。

今回作ったテスト環境はこちらです。JSはテストコードも含めてES6形式で記述できるようにしてあります(babelで変換)。Karma + PhantomJS + Mocha + power-assertを組み合わせたクライアントJSのテスト環境となりました。

▼ フロント開発の関連記事はこちら

使い方

こちらからテンプレートをclone、もしくはダウンロードします。

まず依存モジュールのインストールをします。必要であればsudoをつけて実行してください。

sudo npm install

テンプレートの構成

src

ES6で書くコードの置き場所

lib

srcフォルダのコードの出力先(babelで変換したコード)

test

テストコードの置き場所

コマンド

package.jsonのscriptsに設定しておくことで npm run {コマンド} を実行することができます。

package.json 抜粋

"scripts": {
    "build": "babel src --out-dir lib --source-maps inline",
    "watch": "babel src --out-dir lib --watch --source-maps inline",
    "test": "npm run build && karma start"
},

 

build

srcフォルダのソースをbabelでコンパイルしてlibフォルダに出力します。

npm run build

 

watch

srcフォルダのソースの変更を検知、babelでコンパイルしてlibフォルダに出力します。

npm run watch

 

test

buildタスクを走らせた後でテストを実行するようにしました。

npm run test

 

使用したモジュールの一覧

今回のユニットテスト環境には下記のモジュールを使用しています。
最小構成を目指していたのですが、クライアントサイドのJSのテストをおこなうためにはブラウザ上で実行する必要があり、KarmaやPhantomJSを導入しています。

babel

ES6でコードを書きたかったので導入しました(テストには関係ありません)。
テンプレートリテラルが地味に便利です。

https://github.com/babel/babel/

Mocha

Mochaの方がJamineより流行ってそう……!という雰囲気を察してMochaを選んでいます。

https://github.com/mochajs/mocha

power-assert

Mochaならassertライブラリにはpower-assertでしょ……!みたいな雰囲気を察しました。

https://github.com/power-assert-js/power-assert

Karma

Node.js上で動作するテストランナー、JSのテストをブラウザ上でおこなうために導入しました。

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

webpack

ES6で書かれたテストコードをテスト実行前にES5形式に変換するために使っています。

https://github.com/webpack/webpack

PhantomJS

ヘッドレスブラウザ(画面のないブラウザ)、Karmaから立ち上げてテストを実行させます。PhantomJSはその他WebサイトのE2Eテストや、スクリーンキャプチャを撮るなど、スクレイピングにも使うことができます。

https://github.com/ariya/phantomjs

主だったモジュールの選定理由や役割を簡単に列挙してみました。残りはpackage.jsonを参照してください。

実際にテストを書いてみる

よくあるDOM要素にclass名をつけたり削除したりといった関数を定義しました。このソースのユニットテストを書いてみましょう。

src/Util.js

let Util = {
    hasClass: function(ele,cls) {
        let tmp = ele.className.split(' ');
        return tmp.indexOf(cls) !== -1;
    },
    addClass: function(ele,cls) {
        if (!this.hasClass(ele,cls)) {
            ele.className += ` ${cls}`;
        }
    },
    removeClass: function(ele,cls) {
        let tmp = ele.className.split(' ');
        let index = tmp.indexOf(cls);
        if(this.hasClass(ele,cls)){
            tmp.splice(index, 1);
            ele.className = tmp.join(' ');
        }
    }
};
export default Util;

こちらが上記のソースのテストコードになります。Karma + PhantomJSのおかげでテストコード上でDOMを扱うことができます。

test/UtilSpec.js

import Util from '../lib/Util';

describe('Util', () => {

    let div;
    beforeEach(() => {
        div = document.createElement('div');
    });

    it('hasClass', () => {
        assert(Util.hasClass(div, 'hoge') === false);
        div.className = 'hoge';
        assert(Util.hasClass(div, 'foo') === false);
        assert(Util.hasClass(div, 'hoge') === true);
    });

    it('addClass', () => {
        Util.addClass(div, 'foo');
        assert(Util.hasClass(div, 'foo') === true);
    });

    it('removeClass', () => {
        div.className = 'bar';
        Util.removeClass(div, 'bar');
        assert(Util.hasClass(div, 'bar') === false);
    });
});

テストを実行してみると……

npm run test

スクリーンショット 2015-09-28 22.41.22

テストが全て通っていますね。わざとテストをコケさせてみましょう。

removeClassの判定を逆にしてみました。

it('removeClass', () => {
        div.className = 'bar';
        Util.removeClass(div, 'bar');
        assert(Util.hasClass(div, 'bar') === true);
    });

テストを実行すると……

スクリーンショット 2015-09-28 22.44.19

power-assetのおかげでコケている原因がわかりやすく表示されています。

おわりに

いかがでしたでしょうか?
クライアントサイドJSのテストの場合、実行環境がブラウザとなるので、テスト環境の構築も一手間かかりやはり面倒だなと正直思いました。

逆に言うと、テスト環境さえできてしまえばテストを書くこと自体は気軽に始めることができます。この機会に、興味はあるけど踏みだせなかったという方はぜひ試してみてください。