2011年8月27日

JavaScriptMVC でテストを実行

実装を進める前にテストを動かしておきます。 あちこちと実装が散らかってくるとデグレードが多くなりますので、 出来るだけ手戻りがないように用意できることが望ましいと思います。

この記事の続きです。

2つのテスト方法

JavaScriptMVC で利用可能なテストには次の2種類があります。

  • QUnit - jQuery プロジェクトで使われているテストスイート
  • FuncUnit - QUnit 上に構築したテストフレームワーク

それぞれのテストはブラウザでも確認できますし、 コマンドラインからも実行できます。 コマンドラインではブラウザをシミュレートするために Envjs を使います。

モデルの取得や生成を確認するためには QUnit を使い、 ユーザーとのインタラクション (e.g. マウス操作、キーボード入力) を確認するためには FuncUnit を使います。 それぞれの方法を順番に見ていきましょう。

QUnit

テスト用の HTML ファイルを cookbook/qunit.html として用意します。

<html>
    <head>
        <link rel="stylesheet" type="text/css" href="../funcunit/qunit/qunit.css" />
    </head>
    <body>
    <h1 id="qunit-header">Cookbook Application Test Suite</h1>
    <h2 id="qunit-banner"></h2>
    <div id="qunit-testrunner-toolbar"></div>
    <h2 id="qunit-userAgent"></h2>
    <ol id="qunit-tests"></ol>
    <div id="qunit-test-area"></div>
    <script type='text/javascript'
            src='../steal/steal.js?cookbook/qunit.js'></script>
    </body>
</html>

テストケースを呼び出すためのスクリプトを cookbook/qunit.js として用意します。 steal.js に渡している部分のパスです。

steal
    .plugins('funcunit/qunit')
    .then('test/qunit/recipe_test')
    ;

test/qunit/recipe_test.js は scaffold で生成されたものです。

ブラウザーで cookbook/qunit.html を開くとテストが実行されます。 まずはすべてのテストケースが失敗しています。

テストケースをクリックすると、以下のメッセージを確認できます。

Died on test #1: Cookbook is not defined

モデルモジュールのローディングミスなので、 cookbook/qunit.js を次のように変更します。

steal
    .plugins('funcunit/qunit', 'jquery/model')
    .then('models/recipe')
    .then('test/qunit/recipe_test')
    ;

再度テストを実行すると、モデルを取得 / 生成するために GET / POST リクエストが発生します。 サーバーサイドは何も実装していませんので、すべてがエラーになります。

サーバーサイドの実装とクライアントサイドの実装は無関係に進めたいですから、 jQuery.fixture を使ってリクエストをエミュレートします。 cookbook/qunit.js で fixture プラグインも読み込むようにします。

steal
    .plugins('funcunit/qunit', 'jquery/model', 'jquery/dom/fixture')
    .then('models/recipe')
    .then('test/qunit/recipe_test')
;

これでテストがパスします。

コマンドラインからの実行

コマンドラインからもテストを実行できます。

$ ./funcunit/envjs cookbook/qunit.html
Using Default Settings
MODULE Model: Cookbook.Models.Recipe

--findAll--
steal.js INFO: looking for fixture in ../cookbook/fixtures/recipes.json.get
  PASS
  PASS
  PASS
  PASS
  done - fail 0, pass 4

--create--
steal.js INFO: using a dynamic fixture for /recipes
steal.js INFO: Model.js - publishing recipe.created
  PASS
  PASS
  PASS okay, expected: "dry cleaning" result: "dry cleaning"
steal.js INFO: using a dynamic fixture for /recipes/1867
  done - fail 0, pass 3

--update--
steal.js INFO: using a dynamic fixture for /recipes
steal.js INFO: Model.js - publishing recipe.destroyed
steal.js INFO: Model.js - publishing recipe.created
  PASS okay, expected: "chicken" result: "chicken"
steal.js INFO: using a dynamic fixture for /recipes/25832
steal.js INFO: Model.js - publishing recipe.updated
  PASS okay, expected: "steak" result: "steak"
steal.js INFO: using a dynamic fixture for /recipes/25832
  done - fail 0, pass 2

--destroy--
steal.js INFO: using a dynamic fixture for /recipes/undefined
steal.js INFO: Model.js - publishing recipe.destroyed
steal.js INFO: Model.js - publishing recipe.destroyed
  PASS Destroy called
  done - fail 0, pass 1


ALL DONEe - fail 0, pass 10

10個のテストがすべてパスしていることが分かります。

FuncUnit

続いて、ユーザー操作に関するテストを作成します。 クリックすべき要素があるか、入力フィールドが存在するか、といったことをテストします。

テスト用の HTML ファイルを cookbook/funcunit.html として用意します。

<html>
    <head>
        <link rel="stylesheet" type="text/css" href="../funcunit/qunit/qunit.css" />
        <title>FuncUnit Test</title>
    </head>
    <body>

        <h1 id="qunit-header">funcunit Test Suite</h1>
        <h2 id="qunit-banner"></h2>
        <div id="qunit-testrunner-toolbar"></div>
        <h2 id="qunit-userAgent"></h2>
        <ol id="qunit-tests"></ol>
        <script type='text/javascript'
                src='../steal/steal.js?cookbook/funcunit.js'></script>
    </body>
</html>

テストケースを呼び出すためのスクリプトを cookbook/funcunit.js として用意します。 test/funcunit/recipe_controller_test.js は scaffold で生成されたものです。

steal
    .plugins('funcunit', 'jquery/model', 'jquery/controller')
    .then('controllers/recipe_controller')
    .then('test/funcunit/recipe_controller_test')
    ;

ブラウザで funcunit.html を開いてテストを実行します。 ポップアップブロックは解除しておきましょう。

コマンドラインからの実行

コマンドラインからも確認しましょう。

$ ./funcunit/envjs cookbook/funcunit.html
Using Default Settings
Selenium is not running. Please use steal/js -selenium to start it.

FuncUnit をコマンドラインから実行すると、裏側で Selenium を動かす必要があります。

$ pushd funcunit/java
$ ln -s selenium-server-standalone-2.0b3.jar selenium-server.jar
$ popd
$ ./js -selenium
$ ./funcunit/envjs cookbook/funcunit.html

たぶんこれで動くはずなんですが、手元の環境では Firefox が何も進みませんでした。 読み込むプラグインが足りないんだと思いますが、深追いしないことにします。

終わりに

JavaScriptMVC を使ってテストケースを動かしました。 2種類のテストがありますので、着目する部分に応じてテストを使い分けられると良さそうです。 ブラウザテストは同じ操作を再現することが煩雑だったりクロスブラウザでの確認を忘れがちですが、 Envjs を利用してコマンドラインからも実行できると、CI ツールに組み込んで使えます。

先にテストを書いておくと、とりあえず今日はここまで、という区切りを考えやすくなるかもしれません。 また、サーバーサイドの API が未実装でも、わざわざモックを用意することなく、 JSON でなんとなくデータをエミュレートできます。 最近は HTML5 を中心としたクライアントサイドのストレージも使えるようになりましたので、 HTTP をエミュレートしない場合には選択肢のひとつとして考慮すべきだと思います。

次は、モデルやコントローラーの API を HTML ドキュメントとして出力します。

コメントを投稿