目次


JavaScript のユニット・テストを効率よく行う

さまざまなブラウザーに対する JavaScript コードのテストを自動化する

Comments

問題のある JavaScript コードの例

Web アプリケーションにとって最大の難題の 1 つは、各種 Web ブラウザーのさまざまなバージョンをサポートしなければならないという問題です。Safari で正常に実行される JavaScript コードが必ずしも Windows Internet Explorer (IE) や Firefox、Google Chrome で正常に動作するとは限りません。この問題の原因は、プレゼンテーション層に存在する JavaScript コードを最初の段階でテストしていないことにあります。この JavaScript コードに対してユニット・テストを実行しない場合には、組織は新しいブラウザーにアップグレードした後、または新しいブラウザーをサポートした後、Web アプリケーションのテストを繰り返すために費用をかけることになります。この記事では、JavaScript コードのユニット・テストを効率的に行うことによってテスト・コストを削減する方法を説明します。

JavaScript を使用したよくある例のひとつが、ログイン・フォームを JavaScript で検証する例です。リスト 1 のフォームを考えてみてください。

リスト 1. ログイン・フォーム
<FORM>
	<table>
		<tr>
			<td>Username</td>
			<td><input type="text" id="username"/></td>
			<td><span id="usernameMessage"></span></td>
		</tr>
		<tr>
			<td>Password</td>
			<td><input type="password" id="password"/></td>
			<td><span id="passwordMessage"></span></td>
		</tr>	
		<tr>
			<td><input type="button" onclick="new appnamespace.
			ApplicationUtil().validateLoginForm()" value="Submit"/></td>
		</tr>
	</table>
</FORM>

このフォームは単純です。このフォームはユーザー名フィールドとパスワード・フィールドで構成されています。送信ボタンをクリックすると、このフォームに特有の検証が ApplicationUtil によって行われます。ApplicationUtil は、この HTML フォームを検証する JavaScript オブジェクトです。リスト 2 は、この ApplicationUtil オブジェクトのコードを示しています。

リスト 2. 問題のある ApplicationUtil オブジェクトのコード
appnamespace = {};

appnamespace.ApplicationUtil = function() {};

appnamespace.ApplicationUtil.prototype.validateLoginForm =  function(){
	var error = true;
	document.getElementById("usernameMessage").innerText = "";
	document.getElementById("passwordMessage").innerText = "";	

	if (!document.getElementById("username").value) {
		document.getElementById("usernameMessage").innerText = 
		"This field is required";
		error = false;
	}
	
	if (!document.getElementById("password").value) {
		document.getElementById("passwordMessage").innerText = 
		"This field is required";
		error = false;
	}		

	return error;		
};

リスト 2 で、ApplicationUtil オブジェクトは単純な検証を行っています。このオブジェクトはユーザー名フィールドとパスワード・フィールドに値が入力されているかどうかを検証しています。フィールドが空の場合、「This field is required (このフィールドは必須)」というエラー・メッセージが表示されます。

リスト 2 のコードは Internet Explorer 8 と Safari 5.1 では動作しますが、Firefox 3.6 では動作しません。Firefox では innerText プロパティーがサポートされていないからです。多くの場合、(上記のコードや、それと似た他の JavaScript コードでの) 主な問題は、作成された JavaScript コードがクロスブラウザー対応であるかどうかが簡単にはわからないことです。

この問題に対する 1 つの解決策は、ユニット・テストを自動化し、ブラウザーが異なってもコードが正常に動作するかどうかを検証することです

JsTestDriver

JavaScript ユニット・テスト・フレームワークとして非常に優れているものの 1 つが JsTestDriver ライブラリーです。このライブラリーは多様なブラウザーに対して JavaScript コードのテストをすることができます。図 1 に JsTestDriver のアーキテクチャーを示します。

図 1. JsTestDriver のアーキテクチャー
JsTestDriver のアーキテクチャー
JsTestDriver のアーキテクチャー

サーバーはブラウザーからの接続を受け付けると、そのブラウザーに JavaScript テスト・ケースのランナー・コードをロードします。さまざまなブラウザーからの接続に対して、サーバーはこれを繰り返します。ブラウザーからサーバーへの接続はコマンドラインから行うことも、そのブラウザーでサーバーの URL を指定して行うこともできます。いったんブラウザーが接続されると、そのブラウザーはスレーブ・ブラウザーと呼ばれます。サーバーは JavaScript コードをロードし、すべてのブラウザーでテスト・ケースを実行し、その結果をクライアントに返します。

クライアント (コマンドライン) に必要とされる主なものには、以下の 2 つがあります。

  1. JavaScript ファイル — ソース・ファイルとテスト・ファイル
  2. 構成ファイル — ソース・ファイルとテスト・ファイルのロードを調整するためのファイル

このアーキテクチャーは柔軟であり、1 つのサーバーが、ネットワーク上に存在するマシンのさまざまなブラウザーからの接続をいくらでも受け付けることができます。このアーキテクチャーが有用である例として、あるコードが Linux 上で実行されており、テスト・ケースを別の Windows マシンの Microsoft Internet Explorer に対して実行したい、といった場合があります。

JsTestDriver ライブラリーを使用するには、まず最新バージョンの JsTestDriver 1.3.2 をダウンロードしてください。

ユニット・テストのコードを作成する

では、JavaScript のテスト・ケースを作成しましょう。単純にするために、ここでは以下のケースをテストします。

  • ユーザー名の値が空で、パスワードの値が空の場合
  • ユーザー名の値が空で、パスワードの値が空ではない場合
  • ユーザー名の値が空ではなく、パスワードの値が空の場合

リスト 3 に、TestCase オブジェクトを表現する ApplicationUtilTest オブジェクトのコードの一部を示します。

リスト 3. ApplicationUtilTest オブジェクトのコードの一部
ApplicationUtilTest = TestCase("ApplicationUtilTest");

ApplicationUtilTest.prototype.setUp = function () {
/*:DOC += <FORM action=""><table><tr><td>Username</td><td>
<input type="text" id="username"/></td><td><span id="usernameMessage">
</span></td></tr><tr><td>Password</td><td>
<input type="password" id="password"/></td><td><span id="passwordMessage"
></span></td></tr></table></FORM>*/
};

ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty = function () {
	var applicationUtil = new appnamespace.ApplicationUtil();
	
	/* Simulate empty user name and password */
	document.getElementById("username").value = "";
	document.getElementById("password").value = "";	
	
	applicationUtil.validateLoginForm();
	
	assertEquals("Username is not validated correctly!", "This field is required", 
	document.getElementById("usernameMessage").innerHTML);
	assertEquals("Password is not validated correctly!", "This field is required", 
	document.getElementById("passwordMessage").innerHTML);	
};

ApplicationUtilTest オブジェクトは JsTestDriver の TestCase オブジェクトを使用して作成されます。JUnit フレームワークを使い慣れている人であれば、setUp メソッドや testXXX メソッドはおなじみのはずです。setUp メソッドはテスト・ケースの初期化に使用されます。この例では、他のテスト・ケース・メソッドに使用する HTML フラグメントを setUp メソッドを使用して宣言しています。

DOC アノテーションは、HTML フラグメントを宣言するために簡単に使える JsTestDriver の規約です。

testValidateLoginFormBothEmpty メソッドの中で、テスト・ケース・メソッド内で使用する ApplicationUtil オブジェクトを作成しています。このコードは次に、ユーザー名とパスワードの DOM 要素を取得して値を空に設定することで、ユーザー名とパスワードの入力が空の場合をシミュレートしています。validateLoginForm メソッドを呼び出すことで実際のフォームの検証が行われています。最後に、assertEquals が呼び出され、span 要素 usernameMessage に含まれるメッセージと span 要素 passwordMessage に含まれるメッセージが適切であるかどうか (適切なメッセージは「This field is required」です) を確認しています。

JsTestDriver では、以下の構成体を使用することができます。

  • fail("msg") — テストを失敗させる必要があり、メッセージ・パラメーターがエラー・メッセージとして表示されることを示します。
  • assertTrue("msg", actual) — actual パラメーターが真であることをアサートします。真でない場合には、メッセージ・パラメーターがエラー・メッセージとして表示されます。
  • assertFalse("msg", actual) — actual パラメーターが偽であることをアサートします。偽でない場合には、メッセージ・パラメーターがエラー・メッセージとして表示されます。
  • assertSame("msg", expected, actual) — actual パラメーターが expected パラメーターと同じであることをアサートします。同じではない場合には、メッセージ・パラメーターがエラー・メッセージとして表示されます。
  • assertNotSame("msg", expected, actual) — actual パラメーターが expected パラメーターと同じではないことをアサートします。同じ場合には、メッセージ・パラメーターがエラー・メッセージとして表示されます。
  • assertNull("msg", actual) — actual パラメーターがヌルであることをアサートします。ヌルでない場合には、メッセージ・パラメーターがエラー・メッセージとして表示されます。
  • assertNotNull("msg", actual) — actual パラメーターがヌルではないことをアサートします。ヌルである場合には、メッセージ・パラメーターがエラー・メッセージとして表示されます。

他のメソッドのコードには他のテスト・ケースが含まれています。リスト 4 はテスト・ケース・オブジェクトの完全なコードを示しています。

リスト 4. ApplicationUtil オブジェクトの完全なコード
ApplicationUtilTest = TestCase("ApplicationUtilTest");

ApplicationUtilTest.prototype.setUp = function () {
/*:DOC += <FORM action=""><table><tr><td>Username</td><td>
<input type="text" id="username"/></td><td><span id="usernameMessage">
</span></td></tr><tr><td>Password</td><td>
<input type="password" id="password"/></td><td><span id="passwordMessage"
></span></td></tr></table></FORM>*/
};

ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty = function () {
	var applicationUtil = new appnamespace.ApplicationUtil();
	
	/* Simulate empty user name and password */
	document.getElementById("username").value = "";
	document.getElementById("password").value = "";	
	
	applicationUtil.validateLoginForm();
	
	assertEquals("Username is not validated correctly!", "This field is required", 
	document.getElementById("usernameMessage").innerHTML);
	assertEquals("Password is not validated correctly!", "This field is required", 
	document.getElementById("passwordMessage").innerHTML);	
};

ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyUserName = function () {
	var applicationUtil = new appnamespace.ApplicationUtil();
	
	/* Simulate empty user name and password */
	document.getElementById("username").value = "";
	document.getElementById("password").value = "anyPassword";	
	
	applicationUtil.validateLoginForm();
	
	assertEquals("Username is not validated correctly!", 
	"This field is required", document.getElementById("usernameMessage").innerHTML);
	assertEquals("Password is not validated correctly!", 
	"", document.getElementById("passwordMessage").innerHTML);	
};

ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyPassword = function () {
	var applicationUtil = new appnamespace.ApplicationUtil();
	
	document.getElementById("username").value = "anyUserName";
	document.getElementById("password").value = "";	
	
	applicationUtil.validateLoginForm();
	
	assertEquals("Username is not validated correctly!", 
	"", document.getElementById("usernameMessage").innerHTML);
	assertEquals("Password is not validated correctly!", 
	"This field is required", document.getElementById("passwordMessage").
	innerHTML);	
};

さまざまなブラウザーでテストをするように構成する

JavaScript コードをテストするための推奨プラクティスの 1 つは、JavaScript ソース・コードを JavaScript テスト・コードとは別のフォルダーに配置することです。図 2 の例では、JavaScript ソースのフォルダーを「js-src」という名前に、JavaScript テストのフォルダーを「js-test」という名前にし、「js」という親フォルダーの下に配置しました。

図 2. JavaScript テスト・フォルダーの構造
JavaScript テスト・フォルダーの構造
JavaScript テスト・フォルダーの構造

ソース・フォルダーとテスト・フォルダーの両方の構造を決めたら、構成ファイルを作成する必要があります。デフォルトで、JsTestDriver ランナーは jsTestDriver.conf という名前の構成ファイルを探します。構成ファイルの名前はコマンドラインから変更することができます。リスト 5JsTestDriver 構成ファイルの内容を示しています。

リスト 5. JsTestDriver 構成ファイルの内容
server: http://localhost:9876

load:
  - js-src/*.js
  - js-test/*.js

この構成ファイルは YAML フォーマットで作成されています。server ディレクティブはテスト・サーバーのアドレスを指定し、一方 load ディレクティブは、どの JavaScript ファイルをどういう順序でブラウザーにロードするかを示しています。

ここで、IE、Firefox、Safari ブラウザーでテスト・ケース・クラスを実行してみましょう。

テスト・ケース・クラスを実行するためにはサーバーを起動する必要があります。以下のコマンドラインを実行すると、JsTestDriver サーバーを起動することができます。

java -jar JsTestDriver-1.3.2.jar --port 9876 --browser "[Firefox Path]",
          "[IE Path]","[Safari Path]"

このコマンドラインを使用すると、サーバーはポート 9876 で起動され、マシン上にある Firefox、IE、Safari ブラウザーからの接続を受け付けます。

ブラウザーを起動し、ブラウザーからサーバーへ接続すると、以下のコマンドラインを実行することでテスト・ケース・クラスを実行することができます。

java -jar JsTestDriver-1.3.2.jar --tests all

上記のコマンドを実行すると、最初の実行結果が表示されます (リスト 6)。

リスト 6. 最初の実行結果
Total 9 tests (Passed: 6; Fails: 3; Errors: 0) (16.00 ms)
  Firefox 3.6.18 Windows: Run 3 tests (Passed: 0; Fails: 3; Errors 0) (8.00 ms)
    ApplicationUtilTest.testValidateLoginFormBothEmpty failed (3.00 ms): 
	AssertError: Username is not validated correctly! expected "This field 
	is required" but was "" Error("Username is not validated correctly! 
	expected \"This field is required\" but was \"\"")@:0()@http://localhost
	:9876/test/js-test/TestApplicationUtil.js:16

    ApplicationUtilTest.testValidateLoginFormWithEmptyUserName failed (3.00 ms): 
	AssertError: Username is not validated correctly! expected "This field is 
	required" but was "" Error("Username is not validated correctly! expected 
	\"This field is required\" but was \"\"")@:0()@http://localhost:9876/test
	/js-test/TestApplicationUtil.js:29

    ApplicationUtilTest.testValidateLoginFormWithEmptyPassword failed (2.00 ms): 
	AssertError: Password is not validated correctly! expected "This field is 
	required" but was "" Error("Password is not validated correctly! expected 
	\"This field is required\" but was \"\"")@:0()@http://localhost:9876/test/
	js-test/TestApplicationUtil.js:42
	
  Safari 534.50 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (2.00 ms)
  Microsoft Internet Explorer 8.0 Windows: Run 3 tests (Passed: 3; Fails: 0; 
  Errors 0) (16.00 ms)
Tests failed: Tests failed. See log for details.

リスト 6 を見ると、Firefox の場合に主な問題があることがわかります。このテストでは、Internet Explorer の場合も Safari の場合も、テスト対象のコードは正常に実行されました。

JavaScript コードを修正し、テスト・ケースを再度実行する

では、問題のある JavaScript コードを修正しましょう。ここでは、innerText の代わりに innerHTML を使用することにします。リスト 7 は修正した ApplicationUtil オブジェクトのコードを示しています。

リスト 7. 修正した ApplicationUtil オブジェクトのコード
appnamespace = {};

appnamespace.ApplicationUtil = function() {};

appnamespace.ApplicationUtil.prototype.validateLoginForm =  function(){
	var error = true;
	document.getElementById("usernameMessage").innerHTML = "";
	document.getElementById("passwordMessage").innerHTML = "";	

	if (!document.getElementById("username").value) {
		document.getElementById("usernameMessage").innerHTML = 
		"This field is required";
		error = false;
	}
	
	if (!document.getElementById("password").value) {
		document.getElementById("passwordMessage").innerHTML = 
		"This field is required";
		error = false;
	}		

	return error;		
};

--test all コマンドライン引数を使用して、このテスト・ケース・オブジェクトを再度実行します。リスト 8 は 2 回目の実行結果を示しています。

リスト 8. 2 回目の実行結果
Total 9 tests (Passed: 9; Fails: 0; Errors: 0) (9.00 ms)
  Firefox 3.6.18 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (9.00 ms)
  Safari 534.50 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms)
  Microsoft Internet Explorer 8.0 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) 
  (0.00 ms)

リスト 8 を見るとわかるように、今度はこの JavaScript コードは IE、Firefox、Safari で問題なく動作します。

まとめ

この記事では、JavaScript のユニット・テストのための最も強力なツールの 1 つ (JsTestDriver) を使用して、さまざまなブラウザーに対して JavaScript のアプリケーション・コードをテストする方法を学びました。さらに、JsTestDriver の概要、JsTestDriver の構成方法、そして Web アプリケーションの中で JsTestDriver を使用して JavaScript コードの品質と堅牢さを確実にする方法についても学習しました。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Java technology
ArticleID=767326
ArticleTitle=JavaScript のユニット・テストを効率よく行う
publish-date=10282011