こんにちは、フロントエンドエンジニアのいなばです。
今回はJSのユニットテスト環境を試行錯誤しながら構築したので、公開してみたいと思います。「クライアントサイドでのJSのユニットテストを書きたいけど環境構築がよくわからない!」という方や、「面倒臭いからテスト書かない!」という方の参考になれば幸いです。
今回作ったテスト環境はこちらです。JSはテストコードも含めてES6形式で記述できるようにしてあります(babelで変換)。Karma + PhantomJS + Mocha + power-assertを組み合わせたクライアントJSのテスト環境となりました。
▼ フロント開発の関連記事はこちら エンジニアがいい感じにフロントエンド開発を爆速化できる環境構築の手順 いい感じにコーディングを爆速化できるフロントエンド開発環境をリニューアル GWに復習したいフロントエンドエンジニア開発系勉強記事まとめ
使い方
こちらからテンプレートを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
テストが全て通っていますね。わざとテストをコケさせてみましょう。
removeClassの判定を逆にしてみました。
it('removeClass', () => { div.className = 'bar'; Util.removeClass(div, 'bar'); assert(Util.hasClass(div, 'bar') === true); });
テストを実行すると……
power-assetのおかげでコケている原因がわかりやすく表示されています。
おわりに
いかがでしたでしょうか?
クライアントサイドJSのテストの場合、実行環境がブラウザとなるので、テスト環境の構築も一手間かかりやはり面倒だなと正直思いました。逆に言うと、テスト環境さえできてしまえばテストを書くこと自体は気軽に始めることができます。この機会に、興味はあるけど踏みだせなかったという方はぜひ試してみてください。
LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。