ディップ Advent Calendar の3日目です。
※トランスパイラ項にECMAScriptについて追記しました。(12/5)
概要
自分は普段バックエンド側を担当しており、最近になってJavaScriptのテスト自動化について勉強を始めました。
そこで、自分と同じようなJavaScriptテスト自動化の初心者の方に向けて、自分が一通りの環境を構築する中で得た内容を共有したいと思います。
キホンのキということで、導入にあたっての前提知識のような内容を主に記述します。
JavaScriptテスト自動化の特徴
特に目立つポイントとして構成要素が多いことが挙げられます。
Webで記事を探してみると
「○○と▲▲と□□と◆◆を使ったJavaScriptのテスト自動化!」
みたいな色々なものを組み合わせたタイトルの記事が多く見つかります。
これらについて前提知識が無いと「○○」や「▲▲」が何なのか分からず、ヤサイマシマシアブラカラメ
だったり、
トールアイスライトアイスエクストラミルクラテ
のような謎の呪文に見えてしまいます。
そこで、これらの謎の呪文の構成要素について解説をしていきます。
テスト自動化における構成要素
1. テストフレームワーク
※ 皆さんご存知の内容かと思いますが、改めて書いています。
テスト自動化にあたり、我々はテストコードを書く必要があります。
しかし、何も道具が無い状態ではテストコードを書くにも多大な労力が伴います。
例えば以下のような足し算をする関数があった場合、
function add(x, y) {
return x + y;
}
これに対するテストコードを自力で書くとしたら
if(add(1, 2) == 3) {
console.log("テスト結果OK");
} else {
console.log("テスト結果NG");
}
のように、ひたすら泥臭いコードを書くハメになる可能性があります。(これは極端な例ですが)
また、このままではテスト実行後の結果がとても分かり辛いです。
どんなテストに成功したのか/失敗したのか、全体でどれだけテストがあって、どれだけパスしたのか。
はたまた失敗した場合に、どんな部分がNGだったのか。
これらを解決するためにあるのが、テストフレームワークと呼ばれるものです。
Javaで言えばJUnit、RubyではRSpec、PHPではPHPUnitやPHPSpecなどが該当します。
こういったテストフレームワークを使うことで、テストを簡潔に分かりやすく書けるようになります。
また、こんな感じでテスト結果も見やすく出力してくれます。
(下図はRSpecでのテスト実行結果の様子です)
テストの成否具合や実施内容、問題となる内容が詳しく出力されていますね。
さて、このテストフレームワークですが、JavaScriptにも当然あります。
よく見かけるものでは、**MochaやJasmine**が挙げられます。
これらのテストフレームワークを使用することで、JavaScriptでも効率的にテストを書くことができます。
※テストフレームワークのお供その1(テストダブル)
便利なテストフレームワークですが、痒いところに手が届かないケースが出てきます。
例えば、ある関数funcA()
からさらに呼び出される別の関数funcB()
について、適切に呼び出されたかを確認するような複雑なケースをテストしたい場合、Mochaだけでは上手くテストできません。
function funcA() {
// 色んな前処理前処理
// ここでarg1からarg3までが用意される
...
funcB(arg1, arg2, arg3); // ← ここで適切なパラメータが渡されるか確認したい
...
// 後処理
}
こういったテストケースを解決するための道具として、テストダブルというものがあります。
このテストダブルを用いると、前述の例で言うところのfuncA()
に対しfuncB()
のフリをする影武者を忍び込ませることができ、その影武者を操ることでfuncB()
がどのように呼ばれたかを確認できるようになります。
Mochaのお供として使用されるテストダブルとして、**Sinon.js**と呼ばれるものがあります。
Mochaを含めた構成では度々登場しますので、覚えておくと良いでしょう。
※Jasmineには標準でテストダブルの機能が含まれています。
※テストフレームワークのお供その2(アサーションライブラリ)
テストフレームワークの役割としてテスト結果を見やすく出力するというものがあります。
この、テスト結果の出力に特化した機能を持つものとして、アサーションライブラリと呼ばれるものがあります。
前述のMochaではこのアサーションライブラリを用いることで、テスト結果をより見やすく拡張できます。
有名なアサーションライブラリとしては、power-assert(power-assert.js)や**Chai**が挙げられます。
特にMochaを使用する場合power-assertも併せて使用する構成が多いため、覚えておくと良いでしょう。
ここまでのおさらい
- テスト自動化をするためにはテストコードを書かねばならない
- テストコードを効率良く書くため、テストフレームワークが存在する
- JavaScriptではMochaやJasmineというテストフレームワークがある
- Mochaを使う場合は、Sinon.jsやpower-assertなども併せて使うと良い
おぉ、なんだかテストコードが書けそうな品揃えができましたね!
あとは各々について詳しく調べてテストコードを書くだけです!
めでたしめでたし。
・・・
はい、勘の良い方なら気付いたと思います。
確かにテストコードは書けそうな雰囲気ですが・・・
そのテストコードはどこで実行するの?
そうなんです。
前段の例で挙げたRSpecやPHPUnitなどはそれぞれrubyやphpで動くものなので、ターミナル上でコマンドを叩けば実行できます。
コマンドで実行できるということはCIツールなどでも簡単に呼び出せます。
では、JavaScriptの場合はどうなるのでしょうか。
WebアプリケーションにおけるJavaScriptの動作環境は・・・そう、ユーザのブラウザ上ですね。
ブラウザ上で実行するとした場合、どうやってテストコードを読み込ませるのか?
いわゆるブラウザに搭載されている開発ツールを使うとしても、手動で準備する必要があるのでは?
そうなってしまうと、もはやテスト自動化と言えないのでは?
何やら怪しい空気が立ち込めてきました。
しかし、冒頭に出てきた謎の呪文の中にこれらの解決方法があります!
というわけで、ここからある意味本編とも言える、JavaScriptテストコード実行基盤の話へと移ります。
2. JavaScript実行環境
これも有名なため改めて書くのも気が引けるのですが・・・
テスト自動化にあたり、ブラウザ以外の場所でJavaScriptを動かす必要が出てきました。
JavaScriptの実行基盤には、言わずと知れた**NodeJSがあります。
改めて説明する必要もありませんね。サーバサイドJavaScriptの立役者です。
JavaScriptのテスト自動化をするにあたり、暗黙の了解のような形でNodeJS**は組み込まれています。
(他にも理由もありますが、それは後述します)
3. トランスパイラ
NodeJSを利用してテストを実施するにあたり、大きな問題があります。
ブラウザ上で動かしていたコードのままでは、NodeJSでうまく動かせないことが多いのです。
分かりやすい例えを出すと、jQueryなどの外部ライブラリを利用しているケースが挙げられます。
通常ブラウザで動かすと想定した場合、外部ライブラリを読み込む場合はどのようにするでしょうか?
<script src="lib/jquery-min.js"></script>
のように、HTML上でScriptタグを記載しますよね。
NodeJSでは直接JavaScriptを実行するため、このようなタグを書いて外部ライブラリを読み込むことはできません。
では、どのようにして外部ライブラリを読み込めば良いのでしょうか。
実はJavaScriptでは、外部ライブラリ読み込み用にimport/export
という構文が用意されています。
NodeJSなどで外部ライブラリを読み込む場合はそちらを利用すれば解決できます。
ということで、NodeJSでテストする場合には、テスト対象のコードでimport/export
を利用するように書き換える必要があります。
しかし、ここでまた問題が発生してしまいます。
このimport/export
ですが、現段階ではどのブラウザでも実装されていないんです。
つまり
- ブラウザで動くコード → NodeJSで動かない
- NodeJSで動くコード → ブラウザで動かない
詰みました。
・・・
と思いきや、ちゃんとこれを解決する救世主がいるんです。
それがトランスパイラです。
トランスパイラとは、あるプログラムソースを、同等の振る舞いをする別のプログラムソースに変換するツールの総称です。
このトランスパイラを使い、テスト実行後にNodeJSで動くコード → ブラウザで動くコードへと変換をかけるようにします。
JavaScriptの変換では**Babelやwebpack、Browserify**が有名です。
※ JavaScriptの書き方について
先ほどimport/export
がブラウザでは動かないという話を書きました。
皆さんも使いたい言語機能が環境によってサポートされてない、なんて事態に遭遇したことがあるかと思います。
よくある理由としては、実行環境でインストールされているバージョンが古いなどですね。
※ うわ、このサーバ未だにPHPのバージョン4系使ってるのかよ・・・みたいな。
あれ、ちょっと待ってください。
JavaScriptのバージョンってどうなってるんでしょうか?
そもそもJavaScriptは、どこの誰が(言語仕様を)作っているのでしょうか?
これについて紆余曲折あったようですが、現在はEcma Internationalという団体が決めた仕様がデファクトスタンダードとして扱われています。
この団体によってECMAScriptという名前で標準化され、ECMA 2015(別名:ES6)や ECMA 2016 といったバージョンがあります。
新しいバージョンのECMAScriptではクラスが使えたり(!)するのですが、残念ながら現状のブラウザではサポートされていません。
・・・あれ、これってさっきのNodeJSのときと同じ状況ですね。
ということで、もうお分かりかと思いますがトランスパイラを使います。
このECMA2015などで書いたJavaScriptは、前述のトランスパイラによってブラウザで動くソースへ変換できます。
そのため、**最新のイケてる仕様でJavaScriptを書きたい!**という場合にもトランスパイラは利用されます。
4. ブラウザ(と、タスクランナー)
前段でブラウザからNodeJSに切り替えたのに、なんでまたブラウザが?と思う方がいるかと思います。
でもよく考えてみてください、NodeJSはJavaScriptを直接実行するんです。
通常のブラウザではどうなっているでしょうか?
ドキュメントとしてHTMLが読み込まれ、その1要素としてJavaScriptが実行されますよね。
この**ドキュメント(DOM)**部分のテストがNodeJSではできないんです。(※一応モジュールを使えばできます)
フロントエンドのJavaScriptはテキストの色やボタンのenabledが変わるなど、DOMへ干渉することが多いです。
それらの振る舞いをテストするには、やはり実際のブラウザで動かすのが一番なわけです。
しかし当然のことながら、自分でブラウザを起動して・・・なんてナンセンスなことはしません。
コマンドから起動できて、自動でテスト実行してくれるようにします。
そこで利用するのがタスクランナーです。
有名なタスクランナーとして**Karmaやgulpが挙げられます。
このKarmaやgulp**を使うことで、ブラウザにテスト対象のコードを読み込ませ、自動でテストが実施できます。
ちなみにこのKarmaやgulpはNodeJSで実行されます。
(前述の、暗黙の了解としてNodeJSが組み込まれる理由はこれです)
また、テストで使用するだけなら、わざわざリッチな見た目のブラウザなんて必要ありませんよね。
そんな時に便利なものとして、ヘッドレスブラウザという画面が描画されないブラウザがあります。
有名なものとして、PhantomJSが挙げられます。
タスクランナーでは実行するブラウザを選択できますので、このPhantomJSを選ぶのも良さそうです。
まとめ
テストフレームワーク
- Jasmine
- Mocha
テストダブル
- SinonJS
アサーションライブラリ
- power-assert
- Chai
JavaScript実行環境
- NodeJS
トランスパイラ
- Babel
- webpack
- Browserify
JavaScript言語仕様
- ECMA 2015(ES6)
- ECMA 2016
タスクランナー
- Karma
- gulp
ヘッドレスブラウザ
- PhantomJS
最後に
長々と書いてしまいましたが、如何でしたでしょうか。
あくまで最初の取っ掛かりとして書いたため、具体的な導入手順や利用方法については記載しませんでした。
実際の導入手順、構成などは参考になるリンクを紹介しますので、そちらを参照いただければと思います。
リンク先の記事について抵抗無く読めるようになっていれば幸いです。
※ 記事中の誤りなどがありましたら、コメントにてご指摘ください。
参考リンク
■ フロントエンドにテストを導入
実際のコードを含め、テストを導入するまでの流れがまとめてあります。
■ Karma + PhantomJS + Mocha + power-assertでクライアントサイドJSのユニットテスト環境を構築してみた
実際に構築されたテスト自動化環境を公開されています。
この記事を読んだ後であればタイトルの呪文が理解できるかと思いますが、いかがでしょうか?
■ ゼロから始めるJavaScript生活
テスト自動化に限らない、最近のJavaScript開発環境におけるツールを紹介されています。