Dojo Objective Harness を利用した Web 2.0 アプリケーションのユニット・テスト

質の高いソフトウェア開発にユニット・テストは欠かせませんが、アジャイル開発とエクストリーム・プログラミングによる開発となると、ユニット・テストが極めて重要な部分となります。これまで Web 2.0 クライアント・サイドのユーザー・インターフェースの自動ユニット・テストは困難で、多くの場合は試みられることもありませんでした。ところが今は、Dojo が提供するユニット・テスト・ハーネスで、JavaScript 機能とユーザー・インターフェースの視覚化の両方を評価することが可能です。このように徹底的にテストされたユーザー・インターフェースでは、最終的に含まれるバグの数が大幅に減ることになります。この記事では、DOH (Dojo Objective Harness) の主要な機能について例を用いて説明し、その優れた機能性をその他の Web 2.0 アプリケーション用テスト・ハーネスと比較します。

Jared Jurkiewicz, Advisory Software Engineer, IBM

Photo of Jared JurkiewiczJared Jurkiewicz は、WebSphere® 製品ファミリーの顧問ソフトウェア・エンジニアです。彼はこれまで WebSphere 組織で、UNIX® オペレーティング・システム・エキスパートから新しいオペレーティング・システムおよびハードウェア・プラットフォームの初期サポート対応におけるリーダーまで、さまざまな職務を果たしてきました。現在の職務は WebSphere FeaturePack for Web 2.0 のリリース・アーキテクトです。また、Dojo Toolkit のコントリビューター兼コミッターでもあります。



Stephanie L. Walter, Advisory Software Engineer, IBM

Photo of Stephanie WalterStephanie Walter は、Tivoli Service Availability and Performance Management アーキテクチャー・チームの顧問ソフトウェア・エンジニアです。以前、Business Monitor Dashboards の開発リーダーを務めた彼女は、Dojo および Web 2.0 技術を広範に扱ってきました。



2008年 10月 21日

徹底的なユニット・テストを行う理由

通常、ユニット・テストはソース・コードの一部をテストするために作成されます。理論上、テスト対象とする部分 (ユニット) は、ソース・コードでテスト可能な最も小さな部分でなければなりません。ユニット・テストは一般に自動化され (必ずしも自動化する必要はありませんが)、その結果は、コードが設計通りの振る舞いをするかどうかを示します。

誰もが知っているように、ソフトウェア開発者は大抵、時間に追われているものです。製品をできるだけ早く市場にリリースしようと極度のプレッシャーの下で作業をしているときに、いったいどんな理由があって、ユニット・テストのコードの作成に時間を割くのでしょうか。その答えは、適切なユニット・テスト・スイートはより品質の高いコードを実現するだけでなく、バグの修正に費やす時間が少なくなることから、最終的には時間の節約にもなるという点にあります。さらに、アジャイル開発方式に従っているとすれば、ソース・コードの作成に取り掛かる前にユニット・テストを作成することで、作成するコードの量は少なる傾向があります。ただすぐにコードの作成に取り掛かるのではなく、その前に設計をじっくり考えることが、ユニット・テストの目標を達成するために作成するコードの量を減らすことになります。


Dojo Objective Harness の概要

エクストリーム・プログラミングやアジャイル開発などに見られるように、ユニット・テストを支持する人は数多くいます。その一方で、Ajax (Asynchronous JavaScript + XML) および Web 2.0 ユーザー・インターフェースが広く使用されるようになったことから、クライアント・サイドのユニット・テストが必要になっています。Dojo Objective Harness (DOH) は、Web 2.0 UI 開発者にとって Java 開発者の JUnit に相当するものです。JSUnit のような既存の JavaScript ユニット・テスト・フレームワークとは異なり、DOH は Dojo を使用するかしないかに関わらず、JavaScript 関数を自動化するためのフレームワークになるだけでなく、ユーザー・インターフェースの実際の視覚化に対するユニット・テストも実行できます。なぜなら、DOH (ぴったりの頭字語です) はテスト・フレームワークに対するコマンドラインとブラウザー・ベース両方のインターフェースを提供しているからです。


ブラウザー環境とブラウザー以外の環境

前述のとおり、DOH はコマンドライン・インターフェースだけでなく、ブラウザー・ベースのインターフェースも提供します。ユニット・テストが完全に自動化されていて、視覚化コンポーネントが何も必要ないのであれば、ビルド・スクリプトによって開始可能で、結果をログに記録できるコマンドライン・インターフェースが最適です。また、コマンドライン・インターフェースは JUnit に非常によく似たユニット・テスト環境になります。DOH がそのコマンドライン・インターフェースに使用しているのは、Java™ コードで作成されたオープンソースの JavaScript エンジン、Rhino です。そのため、documentwindowDOMParser、および XMLHttpRequest オブジェクトへの参照は解決できません。もう 1 つの問題として、Rhino はよく使用されているブラウザーとは別の JavaScript インタープリターを使用するため、あるランタイムではテストに合格しても、別のランタイムでは合格しない可能性があります。

ユニット・テストのビジュアル・コンポーネント、そして各種 JavaScript オブジェクトへのアクセスが必要な場合には、ブラウザー・ベースのインターフェースが確実な方法となります。この場合に注意しなければいけないのは、ブラウザーを使用したユニット・テストは 100% 自動化されるわけではないことです。そのため、目的のブラウザーでユニット・テストを立ち上げて結果を検査する必要があります。しかしこれは、まったく意外なことではありません。UI の表示が「良好」であるかどうかを確認するのは、通常は人間の主観的判断となるからです。ブラウザーでのテスト・ランナーは、ユニット・テストの結果をビジュアル結果、そしてユニット・テスト統計という 2 通りの方法で表示します。図 1 の左側に実行されたテスト・ケースが示され、右側の Test Page タブの下に、コード実行が視覚化されます。

図 1. DOH ユニット・テストの視覚化

クリックして大きなイメージを見る

図 1. DOH ユニット・テストの視覚化

図 2 に、Log タブに表示されたユニット・テスト統計を示します。

図 2. DOH ユニット・テストの統計

クリックして大きなイメージを見る

図 2. DOH ユニット・テストの統計


ブラウザーの互換性

複数のブラウザーの複数のバージョンを対象としてクライアント・サイドのコードを開発している誰もが知っているように、ユニット・テストによってブラウザー間での振る舞いの違いを素早く見つけ出せるかどうかが肝心な点です。DOH テスト・ランナーは単なる HTML とJavaScript なので、どのブラウザーでもユニット・テストを実行できます。これはつまり、FireFox、Internet Explorer、そして Safari (しかもそれぞれに異なるバージョン) でユニット・テストを実行して、結果を相互に比較できるということです。基本となる JavaScript メソッドが複数のプラットフォーム間で同じように振る舞うことを確認できるだけでなく、さまざまなプラットフォームで同じ (あるいは、少なくとも許容できるだけの) 表示がされることも確認できます。あるブラウザーで完璧に見えるウィジェットでも、別のブラウザーではほとんど認識できないことがあるのは周知の事実です。大抵の場合、クロスブラウザー関係のバグは厄介で、修正するのが大変です。ブラウザーの互換性を早期に、しかも自動化された方法でテストすることで、ソフトウェアが市場に出る前に、確実にクロスブラウザー・サポートの問題を明らかにして修正することができます。


使用できるテスト関数

すべてのテスト・フレームワークは、ユニット・テストの結果をチェックするためのメソッドを提供する必要がありますが、DOH も例外ではありません。DOH では、テスト検証で使用するアサーション用 API を 3 つ用意しています (リスト 1 を参照)。

リスト 1. 3 つのアサーション用 API
     doh.assertEqual(expectedResult, actualResult)
     doh.assertFalse(testCondition)
     doh.assertTrue(testCondition)

さらに、この 3 つの関数の簡略バージョンも使用することができます。これらの簡略バージョンはリスト 2 に記載するとおりです。

リスト 2. アサーション用 API の簡略バージョン
     doh.is(expectedResult, actualResult)
     doh.f(testCondition)
     doh.t(testCondition)

アサーションが失敗すると例外がスローされます。例外のタイプが何であろうと、ユニット・テストで例外がスローされた場合、DOH はテスト全体の失敗を宣言します。この点は、テストが例外をスローしてくれることを期待しているとしたら、よく認識しておく必要があります。その場合には、コードを try catch ブロックで囲まなければなりません。ユニット・テストが呼び出されると、DOH は発生したすべてのエラーと、失敗した特定のテストをレポートします。さらに DOH は、実行したテストの合計数、発生したエラーの合計数、失敗したテストの合計数もレポートします。

ユニット・テストを構成するときには、アサーションの数を最小限に抑えるのが最善策です。DOH エラー・レポートの設計によっては、失敗となったアサーションを判断しにくい場合があるためです。通常、等価性を利用したアサーションの場合は、どのアサーションが失敗となったのかを判断するのは比較的簡単ですが、true および false によるアサーションとなると判断が難しくなります。

場合によっては、ユニット・テストで発生したエラーが、アサーションによってスローされたものではないこともあります。このような場合、エラーの原因として最も可能性が高いのは、ユニット・テストが誤っているか、テスト対象のコードが誤っているかのいずれかです。幸い、Firefox の Firebug アドオンを使用すれば、ユニット・テストでの基本的なコード問題をデバッグすることができます。


非同期関数のテスト

クライアント・サイドのアプリケーションの中で行われる非同期呼び出しの振る舞いに対してユニット・テストを実行できたら素晴らしいと思いませんか?DOH がこれを可能にします。Ajax リクエストの振る舞いのテストは、DOH が持つ最も価値ある機能の 1 つです。DOH のブラウザー・ベースのインターフェースは XMLHttpRequest オブジェクトにアクセスすることから、DOH では非同期のユニット・テストをサポートすることができます。テスト・ケースが非同期であることを DOH に指示するため、テスト・ケースは doh.Deferred オブジェクトを返すという手段でアラートを出します。そのテストが非同期であることを DOH が認識しないと、テストのコードが実行された後、DOH はテストが完了し、エラーが発生しなかったと判断します。これは当然、誤検出という結果となり、コードは部分的にテストされないままになってしまいます。

テスト・ケース自体も、非同期のコンテキストを理解した上で作成しなければなりません。doh.Deferred オブジェクトがユニット・テストから返された場合には、非同期呼び出しのすべてのエラーをキャッチして、このオブジェクトの errback メソッドにキャッチしたエラーを渡す必要があります。例外が何も発生しない場合には、パラメーターを true に設定してオブジェクトの callback メソッドを呼び出してください。これにより、DOH は失敗したテストを正確にレポートできるようになります。

非同期テストの作成を容易にするため、doh.Deferred オブジェクトには getTestCallback 関数が用意されています。この関数は、非同期呼び出しのコールバック関数で発生した例外を暗黙的に処理します。必要な作業は、実行するアサーションが含まれるテスト関数を getTestCallback に渡すだけです。これにより、非同期呼び出しの間に発生した例外を手動で処理する手間を省けます。詳細は、「独自のテスト・スイートの作成」のセクションを参照してください。

DOH では、ミリ秒単位のカスタム・タイムアウトを指定することもできます。指定した時間内に応答が返されなければ、テストは失敗します。非同期テストのデフォルト・タイムアウト値は 500 ms (0.5 秒) なので、多くの場合は、テストに失敗しないようにこれよりも長いタイムアウト値を明示的に指定すると良いでしょう。


独自のテスト・スイートの作成

DOH を使って独自のテスト・スイートを作成するのは、初めは難しそうに見えるかもしれませんが、実際はそれほど難しくはありません。DOH フレームワークを使うと、極めて柔軟にテストを定義してロードすることができるので、ロードのフローはそれぞれ固有の構造に合わせて変更されることがよくあります。とは言うものの、Dojo のユニット・テストはほぼすべて、新しいモジュールの所有者が簡単にその構造を理解して実行できるように共通の構造に従っています。DOH が機能する仕組みに慣れるまでは、既存の規約に従うことをお勧めします。


DOH テスト・ケースの基本構造

テスト・ケースの構造について、デモ用モジュールの demo.doh を使用して説明します。このモジュールは、Dojo ディレクトリー構造のピア・モジュールとして配置されます。このようにピア構造になっている理由は、DOH フレームワークは Dojo のモジュール・ローダー構造を使用しており、dojo.registerModulePath() を実行して Dojo にソース・コードがどこに配置されているかを指示しない限り、使用するモジュール・ディレクトリーは Dojo のピア・ディレクトリーであると前提するからです。場所の指定は、util/doh/runner.html を編集してモジュール・パスを登録し、事前に doh.runner をインポートしておくことで簡単に行うことができますが、初心者のユーザーには Dojo の前提に合わせるほうが簡単です。図 3 に、このセクションを通して参照する一般的なディレクトリー構造を示します。

図 3. 一般的なディレクトリー構造
一般的なディレクトリー構造

図 3 に示されているように、有効なプラクティスは、Dojo モジュールそれぞれに、そのモジュール専用のユニット・テストだけを含めることです。こうすれば、モジュール開発者はプロジェクト全体とは切り離してユニット・テストを実行することができます。しかしだからと言って、テスト・スイート・ファイルですべてのモジュールのすべてのユニット・テストをロードすることはできないという意味ではありません。その方法については、構造の基本について詳しく説明した上で、記事の後のほうのセクションで詳しく説明します。


DOH のデモ用テスト・ケース・セット

テストとその動作の詳細を探るには、テスト対象について理解しておくと役立ちます。demo.doh の場合、このモジュールには、ヘルパー関数が含まれるファイルと、単純な DemoWidget があります。ヘルパー関数とウィジェットの両方を使用しているわけは、この両方を使うことで、表示には現れない関数 (JavaScript 関数) と html で直接使用されるウィジェットの両方を、アプリケーションでの使われ方と同じようにテストする方法が効果的に示されるからです。これらのファイルは、理解しやすいように一般的な振る舞いを実装しています。リスト 3 に demoFunctions.js のコンテンツを、リスト 4 に DemoWidget.js の内容を記載します。

リスト 3. demoFunctions.js の内容
dojo.provide("demo.doh.demoFunctions");

//This file contains a collection of helper functions that are not
//part of any defined dojo class.

demo.doh.demoFunctions.alwaysTrue = function() {
  //  summary:
  //    A simple demo helper function that always returns the boolean true when 
  //    called.
  //  description:
  //    A simple demo helper function that always returns the boolean true when 
  //    called.
  return true; // boolean.
};

demo.doh.demoFunctions.alwaysFalse = function() {
  //  summary:
  //    A simple demo helper function that always returns the boolean false when 
  //    called.
  //  description:
  //    A simple demo helper function that always returns the boolean false when 
  //    called.
  return false; // boolean.
};

demo.doh.demoFunctions.isTrue = function(/* anything */ thing) {
  //  summary:
  //    A simple demo helper function that returns true if the thing passed in is
  //     logically true.
  //  description:
  //    A simple demo helper function that returns true if he thing passed in is 
  //    logically true.
  //    This means that for any defined objects, or Boolean  values of true, it 
  //    should return true,
  //    For undefined, null, 0, or false, it returns false.
  //  thing:
  //    Anything.  Optional argument.
  var type = typeof thing;
  if (type === "undefined" || thing === null || thing === 0 || thing === false) {
    return false; //boolean
  }
  return true; // Boolean
};

demo.doh.demoFunctions.asyncEcho = function(/* function */ callback,
                                                    /* string */ message){ 
  //  summary:
  //    A simple demo helper function that does an asynchronous echo 
//     of a message.
  //  description:  
  //    A simple demo helper function that does an asynchronous echo 
//      of a message.
  //    The callback function is called and passed parameter 'message' 
//       two seconds 
  //    after this helper is called.
  //  callback:
  //    The function to call after waiting two seconds.  Takes one
//       parameter, 
  //    a string message.
  //  message:
  //    The message to pass to the callback function.
  if (dojo.isFunction(callback)) {
    var handle;
    var caller = function() {
      callback(message);
      clearTimeout(handle);
      handle = null;
    };
    handle = setTimeout(caller, 2000);
  }
};
リスト 4. demo/doh/DemoWidget.js の内容
dojo.provide("demo.doh.DemoWidget");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");

dojo.declare("demo.doh.DemoWidget", [dijit._Widget, dijit._Templated], 

     //The template used to define the widget default HTML structure.
     templateString: '<div dojoAttachPoint="textNode" style="width: 150px; ' +
          ' margin: auto; background-color: #98AFC7; font-weight: bold; color: ' + 
          'white; text-align: center;"></div>',

     textNode: null,          //Attach point to assign the content to.

     value: 'Not Set',     //Current text content.

     startup: function() {
          //     summary:
          //          Overridden startup function to set the default value.
          //     description:
          //          Overridden startup function to set the default value.
          this.setValue(this.value);
     },

     getValue: function() {
          //     summary:
          //          Simple function to get the text content under the textNode
          //     description:
          //          Simple function to get the text content under the textNode
          return this.textNode.innerHTML;
     },

     setValue: function(value) {
          //     summary:
          //          Simple function to set the text content under the textNode
          //     description:
          //          Simple function to set the text content under the textNode
          this.textNode.innerHTML = value;
          this.value = value;
     }
});

DOH でのスタンドアロン関数 (同期および非同期) のテスト

リスト 3 とリスト 4 を見るとわかるように、2 つのファイルそれぞれに、単純なウィジェットと少数のスタンドアロン関数のセットを実装しました。定義し終わったところで、次の目標は、これらの関数とウィジェットを実行して、期待通りに振る舞うことを確認するユニット・テストを実装することです。他の JavaScript ユニット・テスト・フレームワークでは、同期関数のテストは簡単にできますが、非同期関数の demo.doh.demoFunctions.asyncEcho、そしてウィジェットとなるとそうはいきません。そこで DOH とその機能を導入して、ブラウザーでのウィジェットのテストならびに同期関数のテストに対処します。

手始めとして最も簡単なのは、スタンドアロン関数をテストすることです。スタンドアロン関数のテスト・ケースは、JavaScript 配列を定義するだけで作成できます。この配列にはテスト関数またはテスト・フィクスチャー、あるいはその両方が含まれている必要がありますが、テストする対象がどれだけ複雑であるかによって、どれを使用すればよいかが決まります。大抵の場合、コードをテストするには単純なテスト関数で十分用が足ります。テスト・フィクスチャーを作成する必要があるのは、タイムアウトの変更、セットアップ操作の実行、またはテスト後のデータの削除が必要な場合だけです。関数の配列を定義したら、後はこの配列を DOH に登録するために、2 つのパラメーターを設定して tests.register を呼び出すだけとなります。この 2 つのパラメーターとは、テストのコレクションに割り当てる名前と、テストの配列です。リスト 5 には、demoFunctions.js スタンドアロン関数を対象としたいくつかのテスト・コードを記載しています。

リスト 5. demo/doh/tests/functions/demoFunctions.js の内容
dojo.provide("demo.doh.tests.functions.demoFunctions");

//Import in the code being tested.
dojo.require("demo.doh.demoFunctions");

doh.register("demo.doh.tests.functions.demoFunctions", [
     function test_alwaysTrue(){
          //     summary:
          //          A simple test of the alwaysTrue function
          //     description:
          //          A simple test of the alwaysTrue function
          doh.assertTrue(demo.doh.demoFunctions.alwaysTrue());
     },
     function test_alwaysFalse(){
          //     summary:
          //          A simple test of the alwaysFalse function
          //     description:
          //          A simple test of the alwaysFalse function
          doh.assertTrue(!demo.doh.demoFunctions.alwaysFalse());
     },
     function test_isTrue(){
          //     summary:
          //          A simple test of the isTrue function
          //     description:
          //          A simple test of the isTrue function with multiple permutations of 
           //          calling it.
          doh.assertTrue(demo.doh.demoFunctions.isTrue(true));
          doh.assertTrue(!demo.doh.demoFunctions.isTrue(false));
          doh.assertTrue(demo.doh.demoFunctions.isTrue({}));
          doh.assertTrue(!demo.doh.demoFunctions.isTrue());
          doh.assertTrue(!demo.doh.demoFunctions.isTrue(null));
          doh.assertTrue(!demo.doh.demoFunctions.isTrue(0));
     },
     {
          //This is a full test fixture instead of a stand-alone test function.  
          //Therefore, it allows over-riding of the timeout period for a deferred test.  
          //You can also define setup and teardown function
          //for complex tests, but they are unnecessary here.
          name: "test_asyncEcho",
          timeout: 5000, // 5 seconds.
          runTest: function() {
               //     summary:
               //          A simple async test of the asyncEcho function.
               //     description:
               //          A simple async test of the asyncEcho function.
               var deferred = new doh.Deferred();
               var message  = "Success";
               function callback(string){
                    try {
                         doh.assertEqual(message, string);
                         deferred.callback(true);
                    } catch (e) {
                         deferred.errback(e);
                    }
               }
               demo.doh.demoFunctions.asyncEcho(callback, message);
               return deferred;      //Return the deferred.  DOH will 
                                     //wait on this object for one of the callbacks to 
                                     //be called, or for the timeout to expire.
          }
     }
]);

リスト 5 のとおり、基本的なテストのセットを定義するときには、テストごとに大量のコードは必要ありません。これは、デフォルト・タイムアウトを変更するために、テスト・フィクスチャーを使ってテストを実行しなければならない場合でも同じことです。これらのテストには、ユニット・テストを作成する際の他のベスト・プラクティスの 1 つ、テストはできるだけ単純に、できるだけ小さくするというベスト・プラクティスも示されています。テストごとのアサーションを少数にする理由は、テストでの失敗を DOH がレポートするエラーから素早く切り分けるためです。アサーションが多すぎると、どのアサーションがエラーになっているかを正確に判断しにくくなります。

上記のテストでもう 1 つの興味深い点として注目してほしいのは、非同期テストを作成する一般的な方法です。コールバックは後で実行されるため、失敗が発生した場合には、同期テストのように簡単には DOH の try/catch でその失敗をキャッチできないので、ユニット・テストが代わって考慮する必要があります。そこで、asyncEcho テストではアサーションを try/catch でラップし、あらゆるエラーを deferred.errback(error) 呼び出しによって DOH に渡しています。このようにラップしなければ、このテストはエラーによって失敗したとしても、DOH がレポートするのはテストのタイムアウトだけとなってしまいます。失敗したアサーションがスローしたエラーにより、deferred.callback() を実行できなくなるからです。この関数が実行されなければ、DOH からすると、テストは決して完了したことにはならず、そのためテストはタイムアウトとなります。別の言葉に置き換えると、DOH が非同期テストの合格または失敗を判断する唯一の基準は、この遅延コールバックで操作が呼び出されるかどうかだけです。


DOH でのウィジェットのテスト

前のセクションで示したように、単純なスタンドアロン関数をテストするのは簡単です。関数またはテスト・フィクスチャーの配列を作成して登録すれば、その配列をロードするときに DOH が実行します。これはこれで素晴らしい機能ですが、スタンドアロン関数と表示に現れないコードが JavaScript のすべてではありません。JavaScript は、ブラウザー DOM を操作して一層インタラクティブなルック・アンド・フィールを提供するためにあります。そこで、次はウィジェットをどのようにテストするかについて検討していきます。

テスト対象のウィジェットをインスタンス化する HTML ファイルを Web ブラウザーでロードしなければならないテストの場合、幸いなことに、DOH はこのようなテストを登録するための優れたフレームワークとメソッドを提供します。実質的に DOH が行うことは、iframe 内の HTML ファイルで実行する DOH のインスタンスと、独自の UI とスタンドアロン・テストを実行する DOH のインスタンスとを関連付けることです。ここで注意する点として、スタンドアロン関数のテストとは異なり、ウィジェットのテストは通常、Rhino などの JavaScript インタープリターによるヘッドレス環境では実行できません。

ウィジェットのテストはどのように定義するかと言うと、まず HTML ファイルを定義し、その中で DOH をインスタンス化し、ウィジェットをインスタンス化し、それから実行するテスト関数を定義します。リスト 6 に、DOH を利用して demo.doh.DemoWidget をテストする HTML ファイルのコードを記載します。

リスト 6. demo/doh/tests/widgets/DemoWidget.html の内容
<html>
    <head>
        <title>DemoWidget Browser Tests</title>
        <script type="text/javascript" src="../../../../dojo/dojo.js" 
                djConfig="isDebug: true, parseOnLoad: true"></script>
        <script type="text/javascript">
        dojo.provide("demo.doh.tests.widgets.DemoWidgetHTML");
        dojo.require("dojo.parser");
        dojo.require("doh.runner");
        dojo.require("demo.doh.DemoWidget");

        dojo.addOnLoad(function(){
             doh.register("demo.doh.tests.widgets.DemoWidget", [
                  function test_DemoWidget_getValue(){
                         //     summary:
                         //          Simple test of the Widget getValue() call.
                     doh.assertEqual("default", dijit.byId("demoWidget").getValue()); 
                  },
                  function test_DemoWidget_setValue(){
                         //     summary:
                         //          Simple test of the Widget setValue() call.
                    var demoWidget = dijit.byId("demoWidget");
                    demoWidget.setValue("Changed Value");
                   doh.assertEqual("Changed Value", demoWidget.getValue());
                  }
             ]);
          //Execute D.O.H. in this remote file.
             doh.run();
        });
        </script>
    </head>
    <body>
        <!-- Define an instance of the widget to test. -->
        <div id="demoWidget" dojoType="demo.doh.DemoWidget" value="default"></div>
    </body>
</html>

リスト 6 からわかるように、これは DOH を実行するスタンドアロンのファイルです。これはこれで上出来ですが、このファイルは DOH の UI を表示しないため、テストに合格したか、失敗したかを判断するのは困難です。DOH に、この HTML ファイルを実行すると同時に、UI も表示できるメカニズムがあれば言うことありません。実は、そのメカニズムが DOH には備わっています。DOH に用意されている別のテスト登録関数、doh.registerUrl() では、別個の HTML ファイルにある DOH の runner.html UI を指すことができます。こうすると、HTML ファイルがフレームにロードされ、その HTML ファイルが作成した DOH インスタンスが UI の DOH インスタンスに接続されるので、HTML ページから UI で失敗または合格を表示することも可能になるというわけです。リスト 7 に、テストおよび結果のソースとして URL を登録するモジュール・ファイルのコードを記載します。

リスト 7. demo/doh/tests/widgets/DemoWidget.js の内容
dojo.provide("demo.doh.tests.widgets.DemoWidget");

if(dojo.isBrowser){
     //Define the HTML file/module URL to import as a 'remote' test.
     doh.registerUrl("demo.doh.tests.widgets.DemoWidget", 
                         dojo.moduleUrl("demo", 
“doh/tests/widgets/DemoWidget.html"));
}

仕上げの作業: テスト定義を単一の DOH テスト・スイートにまとめる

個々のテスト・ファイルを作成する方法は、以上です。サンプル・コードからわかるように、単一のテストを作成するのは複雑な作業ではありません。後は、これらのテスト定義を取得して、DOH の UI にロードして実行するだけですが、この作業も難しいことではありません。まず必要なのは、DOH の runner.html にリダイレクトする HTML ファイルを作成することです。この際、リダイレクトの一部として、ロードする JavaScript モジュール・ファイルを定義するクエリー・パラメーターを渡してください。この単一のモジュール・ファイル (通常は module.js と呼ばれます) は、dojo.require() を使用してテスト・ファイルのそれぞれをロードします。dojo.require() がファイルを取り込むと、これらのファイルがテストを登録します。DOH によってすべてのテスト・ファイルがロードされると、フレームワークが自動的にテストを実行します。リスト 8 に記載するのはリダイレクト・ファイルです。リスト 9 は、テスト・ファイルをすべて取り込む module.js ファイルです。

リスト 8. demo/doh/tests/runTests.html の内容
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
  <head>
    <title>demo.doh Unit Test Runner</title>
    <meta http-equiv="REFRESH"         
content="0;url=../../../util/doh/runner.html?testModule=demo.doh.tests.module">
  </head>
  <body>
      Redirecting to D.O.H runner.
  </body>
</html>
リスト 9. demo/doh/tests/module.js の内容
dojo.provide("demo.doh.tests.module");
//This file loads in all the test definitions.  

try{
     //Load in the demoFunctions module test.
     dojo.require("demo.doh.tests.functions.demoFunctions");
     //Load in the widget tests.
     dojo.require("demo.doh.tests.widgets.DemoWidget");
}catch(e){
     doh.debug(e);
}

まとめ

DOH は初心者のユーザーには難しそうに感じられるかもしれませんが、これは柔軟性の高い強力なユニット・テスト・フレームワークになります。DOH はテストを個々のロード可能ファイルにモジュール化し、テストをグループとして関連付ける関数を提供し、実行対象のコードに関する条件をアサーションする一連のテスト API を提供します。さらに、URL を登録し、iframe ページをロードしてウィジェットの非同期テストおよびブラウザー・テストに対応するためのフレームワークにもなります。

DOH の各部分を 1 つひとつ見ていけば、複雑だという印象はなくなるはずです。単純なテスト・ケースは素早く簡単に作成できます。そして、作成したこれらのテスト・ケースをテスト・スイートにまとめる方法は、テストのセットごとに dojo.require() を設定した JavaScript ファイルを 1 つ作成するだけです。このモジュール・ファイルが、テスト・スイートのエントリー・ポイントとなります。DOH はまた、合格、失敗、さらにはスローされたエラーを示す強力な UI にもなります。この機能を利用するには、クエリー・パラメーターでロードするファイルを定義して runner.html をロードすればよいだけの話です。すると、このロードしたファイルがテストを登録します。

最後に付け加えておくと、DOH はブラウザー環境には制限されません。基本的な DOH ローダーとフレームワークは、SpiderMonkey や Rhino などのヘッドレス JavaScript 環境でも使用することができます。JavaScript コードをテストするには、DOH はまさに完全かつ効率的なフレームワークとなります。


ダウンロード

内容ファイル名サイズ
Source codedemo.doh.zip5KB

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=353012
ArticleTitle=Dojo Objective Harness を利用した Web 2.0 アプリケーションのユニット・テスト
publish-date=10212008