WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

JsUnit を使った JavaScript のユニットテスト

アプリケーションを開発する上で、避けて通れないもの、それがテストです。とくにブラウザごとの非互換性が大きい Web アプリケーションでは、念入りなテストが必要です。でも、テストはあまり創造的な作業ではないし、やったからといってなにか機能が増えるわけでもない。できるだけ手間をかけずに済ませたいところですね。

そんなわけで、本日は JavaScript 用のテストフレームワークである JsUnit を利用したユニットテストの方法をご紹介しようと思います。 Ruby のユニットテストの記事でも書きましたが、ユニットテストによるテスト・ファースト開発は開発効率の面でも良い影響があります。まだ導入していない方は、ぜひこの機会に使ってみてください。

JsUnit について

今回利用する JsUnit は Java 用の JUnit を参考にして作られた JavaScript 用のユニットテストフレームワークです。 HTML ファイルにテスト用の JavaScript 関数を記述するだけでテストが実行できます。大規模なプロジェクト向けに Java で組まれたサーバーも用意されているのですが、そこまで手を出すのは大変そうなので、今回はブラウザのみで行う範囲の機能をご紹介します。サーバーを使えば多数のブラウザ・ OS 上でのテストを自動化するなんてことも可能なようですので、興味のある方は挑戦してみてください。

JsUnit をインストールする

まずはこちらのページから最新版の zip ファイルをダウンロードし、適当な場所に展開してください。 "jsunit" というディレクトリが作成され、その下に多数のファイル・ディレクトリが展開されるはずです。ただし、そのうち半分程度は Java サーバー関連や開発用のものなので、今回は必要ありません。以下のファイル・ディレクトリがあれば動作するはずです。

  • app
  • css
  • images
  • testRunner.html

これら以外は、必要に応じて削除してしまってかまいません。あとは、この "jsunit" ディレクトリを適当な場所にコピーすればインストール完了です。ユニットテストは外部入力を使わないのが普通なので、ローカル(file:///〜)で行うのが手軽だと思います。もし Web サーバー上で動かす場合は、 "jsunit" ディレクトリをテスト対象の Web アプリケーションと同じドメインに配置するのが無難です。

テストの方法

当然ながら、ユニットテストを行うためにはテストを行うコードを書かねばなりません。 JsUnit では、テスト用に HTML ファイルをひとつ作成し、その中に関数としてテストケースを記述することになっています。それでは、実際にやってみましょう。

テスト用の HTML を作成

前述のとおり、まずはテスト用のコードを記述するための HTML ファイルを作成します。作成する場所は JsUnit の testRunner.html と同じドメイン内であればどこでもかまいません。ファイル名にも制限はありませんので、わかりやすい名前をつけてください。内容はだいたい以下のような感じです。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Sample Test File</title>
<script language="JavaScript" type="text/javascript" src="jsunit/app/jsUnitCore.js"></script>
<script language="JavaScript" type="text/javascript">
// テスト用のコード
</script>
</head>

<body>
<h1>Sample Test File</h1>
</body>

</html>

とにかくポイントは "jsunit/app/JsUnitCore.js" を読み込むことです(パスは適切に変更してください)。それ以外の記述は適当でも大丈夫でしょう。不可視 IFRAME に読み込まれるだけなので、 body 内は空でもかまいません。

テストケースの関数を記述

次は、上記の HTML ファイル内の「テスト用のコード」というコメントの部分に、テストケースとなる関数を書き入れましょう。具体的には、単に関数名が "test" で始まるグローバル関数を定義するだけです。こうすれば、 JsUnit が自動的に検出してテストとして実行してくれます。

ここでは、例として W3C イベントモデルの addEventListener 関数が定義されているかどうかを検査するテストケースを書いてみました。アプリケーションではなくブラウザの機能テストになってしまっていますが、ご勘弁ください。

function testAddEventListener() {
  var el = document.createElement('DIV');
  assertNotUndefined(el.addEventListener);
}

ここで使っている assertNotUndefined 関数は、引数が undefined あればテストを失敗させます。他にもさまざまな条件の assert 関数がありますが、それに関しては後述します。

ここではテストケースをひとつしか作りませんでしたが、もちろん複数のテストケースを記述することができます。単に "test" で始まる関数を複数定義するだけで、 JsUnit がそれらをすべて実行してくれます。

テストの実行

これで準備完了。さっそくテストを実行してみましょう。それには、まずブラウザで "jsunit/testRunner.html" を開きます。以下のようなページが表示されるはずです。

ここで、ページ上部のテキストボックスに先ほど作成した HTML の URL ("file:///" や "http://" を除いたもの)を入力して「Run」ボタンをクリックすれば、テストが実行されます。正しく標準に準拠したブラウザであれば、以下のようにテストが成功するはずです。

当然ながら、 IE8 以前で実行すると失敗します :P

さて、ユニットテストは頻繁に繰り返すものですから、いちいち URL を入力するのは面倒ですよね。そのため、 testRunner.html の querystring で以下のようにテストファイルを指定できます。

file:///path/to/testRunner.html?testPage=テストページのURL

ここで指定する「テストページのURL」はサーバールートからの相対パスでなければなりません。 "/path/to/test.html" とか、 "c:/path/to/test.html" という感じです。さらに続けて "&autoRun=true" を追加すれば、ページ読み込み後すぐにテストを実行します。これらの指定を追加した URL をブックマークしておけば、簡単にテストができるというわけです。

高度な機能

ここまででテストを実行するための最低限の方法はお分かりいただけたかと思います。しかし、 JsUnit にはより便利にテストを実行する機能がいろいろ実装されています。それらのうちよく使うものをご紹介しましょう。

複数のテストファイルを一気に実行

アプリケーションの規模が大きくなってくると、テストの数もどんどん増えていきます。そうすると、テストを記述する HTML ファイルを複数に分けたくなってしまいますよね。しかし、それらのテストを個別に実行するのは面倒です。

そんなときのために、 JsUnit では複数のテストファイルをまとめて「テストスィート」を作成する機能があります。これを使えば、複数のファイルに分かれたテストケースを一気に実行することができます。

テストスィートも通常のテストファイルと同じくひとつの HTML ファイルとして作成します。ここでは、 "test1.html" と "test2.html" という 2 つのテストファイルがあると仮定して、それらを一気に実行するための "testsuite.html" を作成することにしましょう。内容は以下のようにすれば OK です。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Sample Test Suite</title>
<script language="JavaScript" type="text/javascript" src="jsunit/app/jsUnitCore.js"></script>
<script language="JavaScript" type="text/javascript">

function suite() {
  var suite = new top.jsUnitTestSuite();
  suite.addTestPage('path/to/test1.html');
  suite.addTestPage('path/to/test2.html');
  return suite;
}

</script>
</head>

<body>
<h1>Sample Test Suite</h1>
</body>
</html>

ポイントは suite 関数です。 JsUnit は、読み込んだテストファイルにこの関数が定義されていると、それはテストスィートであると判断し、この suite 関数を実行します。そしてその返り値からテストファイルを取得し、それらを連続実行してくれます。

テストファイルの追加の仕方は、上のコードを見ていただければ一目瞭然でしょう。テストファイルの指定には完全な URL と相対パスのどちらも使えますが、相対パスの場合は "testRunner.html" からの相対パスとなりますので、ご注意ください。

あとは、先ほどと同じように "testRunner.html" をブラウザで開き、単体のテストファイルの代わりにこの "testsuite.html" を指定すれば、記述されたすべてのテストが実行されます。

テストの初期化・後処理

すべてのテストに対して共通の初期化や後処理を行いたいことはよくあります。もちろん適当な関数を用意して明示的に呼び出してもいいのですが、以下の関数を定義しておけば、 JsUnit が勝手に呼び出してくれます。

setUp
各テストケースを実行する直前に毎回呼ばれます。
tearDown
各テストケースの終了後に毎回呼ばれます。
setUpPage
テスト用の HTML を読み込んだ際、 1 回だけ呼ばれます。

これらの関数にはいずれも引数・返り値はありません。

なお、もし setUpPage 関数を定義した場合は、準備が整った時点で setUpPageStatus というグローバル変数に "complete" を代入する必要があります。それまではテストの実行が行われません。これはテストに必要なデータを非同期読み込みできるようにするための仕様です。

ログメッセージの出力

テストケースの関数内では、ログ出力のために以下の関数が利用できます。

  • info(message[, value])
  • warn(message[, value])
  • error(message[, value])

これらで出力したメッセージは、 "testRunner.html" のページにある「Trace level」というコンボボックスでレベルを指定した上でテストを実行すると表示されます。第 2 引数を指定した場合は、 "message: value" という感じで表示されます。

例外が発生しなければテストを失敗させる

これは機能というより Tips ですが、「例外が発生しなければいけないコード」を簡単にテストする方法です。例えば、関数に不正な引数を渡してきちんと例外が発生するかをテストしたいときに便利です。

残念ながらこれを実現するような assert 関数は標準では用意されていないので、自分で作ってしまいましょう。

function assertThrow() {
  var msg, fn;
  if(arguments.length >= 2) {
    msg = arguments[0];
    fn = arguments[1];
  } else if(arguments.length == 1) {
    msg ="Any exception hasn't been thrown in assertThrow";
    fn = arguments[0];
  } else {
    error('Bad argument to assertThrow(function)');
  }
  try {
    fn();
    fail(msg);
  } catch(ex) {
    if(ex instanceof JsUnitException)
      throw ex;
  }
}

そして、テストケースを以下のように書けば OK です。

function testThrow() {
   assertThrow(function() {
     // 例外を投げるコード
   });
}

これで、「例外を投げるコード」内で例外が発生しなかったときにテストが失敗します。

assert リファレンス

JsUnit には実行結果を判定するための assert 関数が多数用意されています。残念ながら公式サイトのドキュメントなどを調べても完全なリストがなかったので、私が調べた限りですが、以下に assert 関数リストを掲載しておきます。

assert([comment,] 値)
が true でなかった場合にテストを失敗させます。が boolean 型でなかった場合も失敗となります。
assertTrue([comment,] 値)
assertと同じです。
assertFalse([comment,] 値)
が false でなかった場合にテストを失敗させます。が boolean 型でなかった場合も失敗となります。
assertEquals([comment,] 値1, 値2)
値1値2が等しくなければテストを失敗させます。型が違う場合も失敗となります。
assertNotEquals([comment,] 値1, 値2)
値1値2が型も含めて等しければテストを失敗させます。
assertNull([comment,] 値)
が null でなければテストを失敗させます。
assertNotNull([comment,] 値)
が null であればテストを失敗させます。
assertUndefined([comment,] 値)
が undefined でなければテストを失敗させます。
assertNotUndefined([comment,] 値)
が undefined であればテストを失敗させます。
assertNaN([comment,] 値)
が NaN でなければテストを失敗させます。
assertNotNaN([comment,] 値)
が NaN であばテストを失敗させます。
assertObjectEquals([comment,] 値1, 値2)
がオブジェクトか配列だった場合に、その要素を再帰的に比較する assertEquals です。
assertArrayEquals([comment,] 値1, 値2)
assertObjectEquals と同じです。
assertEvaluatesToTrue([comment,] 値)
が真でなければテストを失敗させます。真として評価できる式であれば、 boolean 型でなくてもパスします。
assertEvaluatesToFalse([comment,] 値)
が偽でなければテストを失敗させます。偽として評価できる式であれば、 boolean 型でなくてもパスします。
assertHTMLEquals([comment,] 文字列1, 文字列2)
文字列1文字列2が HTML がとして同じでなければ、テストを失敗させます。例えば、 "<div> </div>" と "<div>&#x20;</div>" は同じと解釈されます。ただし、 innerHTML に代入した結果を比較しているため、ブラウザによって判定基準が微妙に違うようです。
assertHashEquals([comment,] 値1, 値2)
値1値2の要素を for 〜 in で列挙して、それぞれの値に対して assertEquals を実行します。 assertObjectEquals と違い、再帰的な比較は行いません。
assertRoughlyEquals([comment,] 値1, 値2, 範囲)
値1値2の差が範囲と同じかより大きいときにテストを失敗させます。
assertContains([comment,] 文字列1, 文字列2)
文字列1文字列2が含まれていなければテストを失敗させます。
fail(comment)
無条件でテストを失敗させます。

以上、今回は JsUnit を使って JavaScript コードのユニットテストを行う方法をご紹介しました。 JavaScript はユーザーとのインタラクションを処理することが多いので、ユニットテストが適用できる部分は限られますが、それでもロジック部分や下位ルーチンなどはきちんとテストしたいところですね。しっかり活用して、信頼性の高い Web アプリケーションを目指しましょう。

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles