クライアントサイド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ならいい感じにテストと付き合っていけるので、是非試してもらいたい。

javascript最強のフレームワーク、dojo入門&簡易チュートリアル

jqueryとかいろんなフレームワークやらライブラリ使って開発していって、プロジェクトがでかくなってくきたり、色んなプロジェクトを行ったり来たりしていると、結構シリアスな問題が出てくる。そんな時はdojoをベースに開発するのが最強という話。
 

追記(2014/08/11):記事を読むのがめんどくさいという人のためにデモを色々作った

今回紹介しきれなかったbuildやtest、coffeescriptについても統合してある。
dojo-coffeescript-jquery-boilerplate demo

ソースはこちら
view sorce on github
この中の動作デモを、ctrl+UでHTMLタグみて、fire bugでコンソールみて、興味を持ちそうなら以下の入門編をどうぞ。
 

dojo入門

dojoはドキュメントは英語だし、やたらと高機能だし、かなりクセがあるし、学習コストが高いから日本で全く流行ってない。ブログでもITメディアでもほとんど紹介されてないし情報も古い。

そんなdojoだけど、シンプルで美しい設計をしている。dojoを使えばシンプルに設計でき、シンプルに実装できる。イケてる実践的なフロントエンド特化のテストフレームワークもあるし、おまけにコードを圧縮してリリースすることも出来る。

抽象的な説明はやめて、以下では、「ボタンを押したら”hello world”と出力する」という単純な仕様を実装しながら、dojoだとどうなるのか見ていくことにしよう。

 

ボタンを押したら”hello world”と出力する(普通のやり方)

javascriptをDOMに適用するには色々手段がある。

まずはinline scriptとして実装する場合。

とりあえずヘッダーでjqueryを読み込み、その下に実装コードを書いた。
inlineのコードは外部ファイル化してsrc=”/ShowHelloButton.jsとして読みこめば複数のページで使いまわせる。
しかしこの実装はちょっとまずい

  1. ヘッダにフレームワークを読み込む処理があるので、その下のDOMがそのフレームワークが読み込まれるまで中断される。
    jquery単体なら問題ないが、どうせnoob javascriptプログラマーはライブラリをぎょうさん読み込むことになるので、プロジェクトの規模が大きくなると、ライブラリやフレームワークの読み込みが終わるまで真っ白な画面をユーザーに見せ続けることになる。
  2. ヘッダのスクリプトはDOMが読み込まれる前に実行されるため、DOMに対する操作はオンロードのイベントとしてセットする必要がある。
    今回はjqeuryを使って$(function(){})として実装してあるが、これでも冗長だし、ダサい

じゃDOMの終了間際に書きましょうということで、以下のように実装してみる

とりあえず上の問題は解決した。が、id=”hello-button”とか、スクリプト依存のidやクラスが気になる。よくあるのが、コーダーがclassやidを変えてしまって「javascript動いてません!」という場合。frontend-hello-worldなど、プレフィックスをつけて、「このプレフィックスが付いたやつは変更しないでね」という解決方法もあるが、その場合でもコードの見通しは悪い。

じゃこんなシンプルなスクリプトは直接DOMに書きましょうって感じで以下のような実装にしてみる。

結構いい感じに思える。コードの行数が少ないし、シンプルで美しい。

ただ、この実装はコードの実行順序の問題が残る。このコードではDOMの最後に読み込んだ外部ソースを参照できない。ということはこのalert(‘hello’)lの部分を外部ファイルに移して参照する場合も結局ヘッダー内で読み込む必要がある。

javascriptのコードの位置だけでもこれだけ悩ましいのだが、他にも問題は山積みである。

  1. javascriptのリファクタリングは地獄である。
    大抵の場合、他の人が書いたjavascriptを読むのは苦痛でしか無い。テストもない
    →dojoなら「タグに対するWidget」という形でコードの役割を明確にすることができるよ!
    →dojoなら名前空間を使ったコードの分離ができるよ!
    →フォルダ構成をきっちり作ればcoffeescriptで全部書けるよ!
    →dojoはdohというイカしたテストフレームワークがあるよ!
  2. 外部ライブラリや外部jsが無限に増えていき、管理不可能となる。
    バージョン違いのjqueryが何個もロードされてたり、似たようなlightboxプラグインが、これまたバージョン違いでlib/フォルダーに置いてあり、どれが使われていないもので、どれを消していいのかわからないという状況になる。さらにpartsだのcommonだのpagesだのcoreだのbuildだのpluginだのlibだのlibraryだの意味不明の同音異義語フォルダが乱立し、そして誰も管理しなくなる
    →dojoなら、require()を使って、依存モジュールは必要な時に呼びだせるよ!2重読み込みもないよ!ついでに圧縮までできるよ!
    →ページごとに一つControllerクラスを作ってそのページで使われているWidgetを全てそのクラスで読みこめば、そのページで何が使われてるか一発で分かるよ!
  3. パフォーマンスが悪い→Controllerクラスを圧縮すると読み込み先の全ての依存モジュールを一つに圧縮できるよ!すごい!

 

dojo簡易チュートリアル

dojoだとこんな感じになる

なんてシンプル!クール!dojoすごい!!実際すごい!
 
 
仕組みを解説すると、まずDOMの下でjqueryとdojoのローカル設定ファイルであるconfig.js、そしてdojo.jsを読み込んでいる。

/javascripts/config.jsはこんな感じで、かならずdojoの前に読み込む必要がある

dojoConfigというグローバル変数に各設定をぶち込んでいく。
 

dojoはAMDローダーという仕組みで、javaでいうimport、PHPでいうrequireみたいな機能を実装していて、javascriptやDOMからいつでもサーバー側のコードを呼び出せるようになっている。

baseUrlでjavascriptの場所を指定し、packageに上位の名前空間を指定しておくと、その名前空間をrequireした場合、その下を探しにいってくれる。

depsの下は、オンロード時に読みこむファイルを指定することができる。ここでは’dojo/parser’を読み込んでおり、parseOnLoad:true と設定することで’dojo/parser’でDOMのパースを実行するように指定してある。
 

実装コードは/javascripts/src/Sample/Views/ShowHelloButton.jsにある

DOMで読み込んだdojo.jsには、基本的にrequireとdefineというグローバル関数しか定義されていない。

今回はそのdefineで、dojo/_base/declareという、dojo流のクラス宣言に使うモジュールと、dijit/_WidgetBaseという、継承することでDOMを拡張できるモジュールと、dojo/onというイベント定義用のモジュールをロードして、ShowHelloWButtonを定義した。
 

まず、declare関数を使って、”ShowHelloButton”というクラスを宣言し、_widgetBaseを継承する。declareの第二引数は配列であり、多重継承ができる。
ちなみに”ShowHelloButton”という部分は適当な名前でもロードされる。dojoはファイル名のみを参照してロードする。この”ShowHelloButton”という名前は内部のidとして使われる。しかし特別な理由がないのならファイル名と同じShowHelloButtonとしておくのが無難。

続いて、postCreateというメソッドをオーバーライドして、DOMが作られたタイミングで実行する処理を記述する

続いて、on_(this.domnode, …)としてクリックのイベントを記述する。
もちろん、jqueryを使って$(this.domNode).on(‘click’ function(){})と記述してもいいが、今回はonのモジュールを使った
this.domNodeには、dojo-data-typeを埋め込んだDOMが入っている。
 

このコードは以下のタグを記述することで自動的に読み込まれる

この部分で事前にSample/Views/ShowHelloButtonをロードしておき、

ここでDOMにWidgetを付与する

testやbuild、coffeescript環境とのマージなど、プロダクトをリリースするまでに必要な技術は後の記事を参考にしてほしい。

dojoはプロのための最強のフロントエンド環境

正直、dojoは軽いプロジェクトで気軽に導入するというレベルのものではない。軽い実装なら軽く済ませるのがスマートなやり方だ。

だけど、webプロジェクトがある程度大きくなってきて、複数人で作業するようになると、javascriptでの開発はどんどん苦しくなってくる。

サーバーサイドできっちり名前空間やパッケージ管理やオブジェクト思考やデザインパターンにしっかり馴染んでいる人なら、dojoで開発環境を整えることに喜びを見いだせるはずだし、そうでない人も是非チャレンジしてほしい。

■参考リンク
dojo公式(英語)
dojo-coffeescript-jquery-boilerplate (git-hub)
dojo-coffeescript-jquery-boilerplate (demo)

 
この記事の続き(Tabを実装しながら、dojoを使ったテストについてチュートリアル形式で学ぶ)
クライアントサイドjavascriptのテストをしたい!そんな時はD.O.H.: Dojo Objective Harness