クライアントサイドjavascriptのテストをしたい!そんな時はD.O.H.: Dojo Objective Harness

今回のページのデモ

クライアントサイドのjavascriptの開発って、簡単だと思われがちだが本当は難しい。

フロントエンドってのはサーバーサイドやコーダーが全て仕事を終えた上にjavascriptを埋め込むって形で開発することが多い。本番のアプリケーションの上で開発して、そこで直接動作確認ってのがよくある状況だと思う。

ただそれだと、そのコード単体で何が本当に要件として必要なのか、その依存関係が不明瞭になる。

javascriptは大抵cssとセットで開発する。例えばタブを開発したとして、そのコードを他のサイトで使う時に関連したcssもコピーする必要がある。
そんな時に、本番アプリケーションの中からタブに関連したjavascriptのコードを抜き出して、HTMLのタグ構成をどうするかとか、idやclassの振り方とか、関連CSSはどれか探すのはすごく難しい。

そうなるとコードの柔軟性が全く無くなってしまって、結局新しくコード書いたほうが速いでしょって感じになる。
そして産業廃棄物見たいなクソコードが蔓延することになって、いつまでたってもフロントエンドは修羅の世界のままになってしまうのだ。

さらに、webのフロントエンドのコードは、実行環境がクライアントによってバラバラなこともあって、javascriptとして正しくても、特定のブラウザで正しく動くかなんて各ブラウザで実行するまでわからない。
単体でテストもできないので、どれがバグの原因なのかを特定するのは相当なハッカースキルが必要となる。そうしてjavascriptプログラマーの変態度が増していき、他の業界のプログラマーから常に失笑を買うようになってしまい、婚期をも逃してしまうのだ。

D.O.H.: Dojo Objective Harnessはすごい!

そんなフロントエンドの悩みを全て解決してくれるのがD.O.H.: Dojo Objective Harness(以下、doh)である。
dohは色々と特徴があるが、実践的に開発する上でのメリットは次の点だろう。

  1. プレーンなHTMLで開発ができる。
    多分フロントエンド開発者が日常的に相手してるのは、カオスで長ったらしいphpやら埋め込まれたHTMLだろう。しかも重くてたまに動作しなかったりする。そんなものを相手に開発する必要なんて無い。
  2. 動作単体でHTML、CSS、javascriptをセットで開発することが出来る
    テストHTMLに関連したCSSを書いておけば、そのHTMLに書いてあるCSSをコピーすればどのサイトでも使えるってことだ。
    気を利かせてsassにしてフォルダ構成をjavascriptと一致させておけば、そのsassをimportするだけでオッケーって事もできる。
  3. 単体テストをまとめてテストできる
    dohは、単体テストを一気にまとめてテストすることができる。
    エラーがでたらそのテストを特定できるし、どのテストが失敗してるかもわかる。
    定期的にブラウザごとにこのテストを実行すれば、そのコード群が全てのブラウザに対応してることが確認できる。

動作してるところをみせろ

そんなことは置いといて、とりあえずコードを書いていこう。

今回は、シンプルなタブを実装しながらテストのある開発をチュートリアル形式で追っていく。

開発手順としては以下の通り

  1. タブを実装しろと頼まれる
  2. デザイナにデザインを上げてもらう
  3. 設計する
  4. (環境構築して)テストHTMLを準備する
  5. コーディングする
  6. テストを書く
  7. 実装する
  8. テストを実行する
  9. バグを取る(5~9は繰り返し)
  10. テストモジュールとして登録する(これで他のテストと一緒にまとめてテストできるようになる。リリース前とかに実行)
  11. 本番HTMLに埋め込む
  12. リリース

1.タブを実装しろと頼まれる

今回はとりあえずページ内のコンテンツをタブで切り替えたいとお願いされたというシチュエーションを想定する。

“jqury tab”や、”dojo tab”で検索してもいい感じのシンプルな実装がなかったのでとりあえず自分で作るという想定。実際はこんな小さなスクリプトはライブラリで構わない

2.デザイナーにデザインを作っててもらう

とりあえずこんな感じと言われた

SimpleTab_design

なるほど、承諾した。

3.設計する

  • タブの中身はHTMLに全て書かれてるとする。タブをクリックした時Ajaxで取り出すなんてのはとりあえず無し。
  • コーダーが組みやすいタグ構成にする。
    タブボタン部分は<ul><li>tab1</li></ul>として、その直後に、コンテンツを<div class=”tabs”><div id=”tabContent1″>content1</div></div>と置く。liにclass=”active”とすることでアクティブ状態を表現する。
  • 開いているコンテンツ以外は全部hideに。
    最初から全部hideにして、スクリプト側でactiveに対応するコンテンツのhideを消す手もあるが、それだと javascriptが読み込まれる前に全て消えた状態になってしまう。
  • 要素を分解すると、タブのボタンと、タブのコンテンツに分かれていて、それぞれの結びつきがある。ってことは、タブのボタンとタブのコンテンツのワンセットをオブジェクトとして考えるのがいいかもしれない。
    タブのボタンに、結びつきのあるコンテンツのIDを指定するようにしよう
  • 現在activeなタブをクリックしたら何も起きないが、そうでない場合、activeなコンテンツを閉じて、クリックされたコンテンツを開く

とりあえずシンプルなタブ実装ってことでこれ以上余計な実装はしない。
タブのボタンが増えた時どうするかとか、そんなのはいい。

4.(環境構築して)テストHTMLを準備する

開発する時のフォルダ構成はこんな感じ

今回はcoffeescriptで開発する。coffeeフォルダに.coffeeを配置し、以下のシェルでウォッチしながらsrcディレクトリにコピーする。cofeescriptのインストールについては割愛

/javascripts/tools/startCoffeeWatch.shに以下のように記述する

これを使って、coffeescriptのwatchを起動しておく

次に、dohを使うためにdojo/dojoと、dojo/utilをダウンロードしておく。
プロジェクトのルート・ディレクトリで以下を実行

util/dohにパスを通すため、またdojoを使うためのconfig.jsを用意する

/javascripts/config.js

また、htmlにアクセスするための、簡易テストサーバーとして、今回はpythonを使う。
pythonが無い場合はapacheでパスを通すなりnodejsを使うなりrubyを使うなり好きにすればいい
プロジェクトのルートで以下を実行

pythonがインストールされていれば、これでテスト環境の構築は終了。

/javascripts/coffee/Sample/tests/Views/SimpleTab.htmlにとりあえず最低限なHTMLを準備しておく

localhost:8000/javascripts/coffee/Sample/tests/Views/SimpleTab.htmlにアクセスして動作を確認する。

これで下準備は完了!

5.コーディングする

とりあえず以下のようにコーディングしてみた。

デザインまで含めちゃってるのでcssがシンプルではないが、本来はここではデザイン部分を分離すべきだ。ここではとりあえずこれで進める。

6.テストを書く7.実装する8.テストを実行する9.バグを取る(5~9は繰り返し)

さて、コードの実装の前にテストを書いていこう。
今回は初めてなので、テストのテストから始める。

c0ffeescriptのウオッチが起動してないなら起動して

/javascripts/coffee/Sample/tests/Views/SimpleTab.coffeeに以下のように書いてみる

/javascripts/coffee/Sample/tests/Views/SimpleTab.htmlに戻り、以下を追記する

localhost:8000/javascripts/coffee/Sample/tests/Views/SimpleTab.htmlにアクセスして、firebugのコンソールを見る
こんな感じになるはず

SimpleTab_test1

次にこんな感じのテストを追加する

するとこんな感じになる

SimpleTab_test2

すごい!

では、実際に実装部分のテストを書いていく。
ただ、javascriptのコードは、DOMへの依存度が高いため、副作用が多く、オブジェクト単体のテストだけで済む場合はほとんどない。

いい感じのテストが思いつかない場合、とりあえず、クライアントサイドのテストって普通だったらどうしてるか?と考える。
普通はURLを開いてポチポチやりながら動作を確認するはずだ。
今回のタブだったら、

  1. classにactiveとついていないタブをクリックしたら、そのタブがactiveになる。
    そのタブ以外のactiveが無くなる
  2. そのタブに結びついたコンテンツ以外がhideになり、
    そのコンテンツのhideが外れる。

こんな感じに実装してテストするはず。

最初のテストは簡単そうだ。早速書いてみる。

“そのタブがactiveになって、そのタブ以外のactiveが無くなる”
というテストを書いてみた。

テストの中でも普通にjqueryが使える。
何もビビる必要はない。

とりあえずこれを実装してみよう

7.実装する

今回はdojoで実装する。別にjqueryだけでもいい

まずはhtml側にSample/Views/SimpleTabを呼び出すコードと(requireの部分)、data-dojo-propsを使って、そのWidgetにターゲットのIdを渡すようにした。

続いてWidgetを実装する

dojoに慣れてない人がほとんだと思うので解説

data-dojo-type属性で、Widgetをそのタグに割り当てることが出来る

data-doijo-props属性で、Widgetの中にkeyと値のセットを流すことが出来る。サーバーサイドとの値の掛け渡しにも便利

dojoのWidgetは、SimpleTab_0, SimpleTab_1と言った具合にidを自動的に振り分ける。
$(@domNode).closest(‘ul’).find(‘[id^=SimpleTab]’)とすることで、同じul内の他のwidgetを含むdomを探すことが出来る。
‘dijit/registry’モジュールを使って、domからwidgetにアクセスする事ができる。
そのwidget内のプロパティやメソッドを呼び出したり値を取得したり変更したりできる。

localhost:8000/javascripts/coffee/Sample/tests/Views/SimpleTab.htmlにアクセスしてテストの結果を確認しよう

SimpleTab_test3

テストが通った!
次は、タブのコンテンツのを入れ替える部分を実装するが、まずはテストを書く

前のテストの結果を踏まえて次のテストが実行される。ほんとはもっといい感じに厳密なテストを書きたかったが、今回は適当。

続いてコードの実装

テストが動いたのを確認して実装完了!

テストモジュールとして登録する

さてこれからが一番おもしろいところ。

/javascripts/coffee/Sample/tests/module.coffeeというファイルを作って、以下のように書き込む

module.coffeeの用意が終わったら以下のURLにアクセスしてみよう
localhost:8000/javascripts/src/util/doh/runner.html?test=Sample/tests/module

SimpleTab_test4

module.coffeeにテスト用htmlを複数登録することで、一気にテストを実行することができる。

これですごいとこは、別にテストがなくてもオッケーってとこ。

テストがなくてもjavascritptが実行時にエラーを吐いてればきちんと「このページでエラー出てますよ」って教えてくれる。

本番ページのリストを登録して、実行時エラーが起きてないか定期的に確認する用途にも使える。

とりあえず、localhost:8000/javascripts/src/util/doh/runner.html?test=Sample/tests/module
というURLは長ったらしくて覚えにくいので、/javascripts/coffee/Sample/index.htmlに、以下の様なHTMLを書くといい

こうすることで、localhost:8000/javascripts/coffee/Sample/にアクセスするだけで、Sampleモジュールのテストが一度に確認できるようになる。
本番リリース前にチェックするのに最適だ。
クロスブラウザのチェックは、各ブラウザでこのURLにアクセスしてみてエラーが無いか確認すればいい
ただし、jqueryとdojoとcoffeescriptの組み合わせならクロスブラウザの問題は滅多に起こらない。

9.本番用htmlに埋め込む

今まで作ったものをサーバーサイド用のテンプレートなんかに埋め込んで行く
基本的にはcssを移植して、ページ下のrequireでモジュールを読み込んでタグを追加するだけだ
バグを発見したら単体テスト用HTMLに戻ってテストと一緒に修正する。

10.リリース

ほんとはコードの圧縮があるのだが、これは別の機会に。
とりあえずsrc以下のフォルダを本番に上げればおk

終わりに

今回はミニマムな要件でテストを書いたため、複雑なテストは書けなかったが、ボタンを押したあと、そのアクションが経過するまで待って、テストを実行し、そのテストの結果を待ってから次のテストを実行するといった、時系列でのテストを明示的に指示することもできる。
時間のかかるアニメーションや、Ajax処理の後に続く処理をテストしたい時も対応可能だ。

上の作業環境の一番いいところは、テストを追加しようと思ったらすぐ追加できること。
「テストなんて必要な時に書けばいい」ってスタンスが一番いいと思う。
私も全てのWidgetにテストを書いてるわけではない。
テストは書かずに普通にF5を繰り返しながら開発するのを基本スタイルにして、そのWidgetに追加機能を実装する段階になって初めてテストを追加したりする。
複雑なアプリケーションの開発なんかはテストを先に書きながらのほうが設計も開発も逆に速くなる。

dohならいい感じにテストと付き合っていけるので、是非試してもらいたい。

h-iwata

最近までReactでフロントエンド構築してました フリーランスなんでサーバーサイド、アプリ、ゲームなどなんでもやります。現在はunityでVRゲーム作ってます。 何か聞きたいことや頼みたいことがあるならコメント欄か、直接facebookまでどうぞ。

コメントを残す

メールアドレスが公開されることはありません。