2012/08/22更新

[JavaScript] 単体テストを行う為にqUnitはいかがでしょうか?内容や使い方を記載しました

このエントリーをはてなブックマークに追加            

こんにちは、最近JSに傾倒中の@yoheiMuneです。
今日は、JavaScriptを単体テストする為のライブラリであるqUnitを調べたので、 その内容をブログに書きたいと思います。

qUnit



qUnitとは

qUnitは、 もともとjQuery, jQuery UI, jQuery Mobileのテストライブラリとして利用されていたもので、 qUnitを使う事で、JavaScriptの単体テストを行うことが出来ます。

JSの単体テストは他にもたくさんありますが、今回はqUnitの紹介をさせて頂きます。
(私自身、JSの単体テストフレームワークを一つずつ見ていく予定で、これが最初の一個です)



qUnitの特徴

qUnitjsには、以下の特徴があります。
  • 基本的には、HTML+JSが必要で、ブラウザでテストページにアクセスしてテストする。
  • Common.jsやJUnitを意識して作られており、他のテストライブラリ経験者なら導入がしやすそう。API説明にも「Common.jsなら××と同じで、jUnitなら○○と一緒」といった書き方が多い。
  • grunt.js(フロントエンドのビルドが出来るツール。詳細は、こちら)に最初から組み込まれたタスクとして使える。

Java出身者の私としては、jUnitに似た構造の点はスゴく良いなと感じました。
jUnitでいうテストメソッドはもちろん、テストメソッド毎のsetUp, tearDown、 テストモジュール(Javaでいうテストクラス)毎のsetUp, tearDownなど、使い勝手が似ていると感じました。



qUnitでHello World

実際に使ってみるには、以下の手順が必要です。
  1. gitHubからqUnitのコードをダウンロードする。
  2. ダウンロードしてきた中身のうち、qunit.jsとqunit.cssを用いて、テスト用のHTMLを作る(コピペです)
  3. テストケースをJSで書く。
  4. テストケースを実行する。

gitHubからqUnitのコードをダウンロードする。

qUnitが公開されているGitからダウンロードしてきます。コマンドは以下で出来ます。
$ git clone https://github.com/jquery/qunit.git


ダウンロードしてきた中身のうち、qunit.jsとqunit.cssを用いて、テスト用のHTMLを作る(コピペです)

以下のHTMLを作成します。 qunit.jsとqunit.cssは、お好みの位置に配置して、その位置をHTML上で指定して下さい。
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>QUnit Example</title>
  <link rel="stylesheet" href="resources/qunit.css">
</head>
<body>
  <div id="qunit"></div>
  <script src="resources/qunit.js"></script>
  <script src="resources/tests.js"></script>
</body>
</html>

上記HTMLのうち、読み込んでいるtest.jsは、次手順で作成するテストケースを記述したファイルとなります。


テストケースをJSで書く。

上記HTMLで読み込んでいるtest.jsにテストケースを書きます。
以下はテストケースのサンプルとなります。
// 成功するテスト
test("hello test", function() {
  ok(1 == 1, "Passed!!");
});

// 失敗するテスト
test("hello test missed", function() {
  ok(1 == 2, "Missed.");
});


テストケースの記述は、testメソッドに記載します。
okメソッドは、第1引数にテストする内容、第2引数にメッセージを記載します。


テストケースを実行する。

上記で作成したHTMLにブラウザからアクセスすると、テストが実行されます。
HTMLのファイルとして読み込んでも、サーバーに配備してHTMLファイルにアクセスしても、どちらでも大丈夫です。
実行された結果が、以下のように表示されれば成功です。
qUnit result



qUnitのAPI仕様

qUnitでは、どんなテスト用APIがあるのかを調べてみました。
以下に紹介する内容は一部で、最新情報はこちらをご参照ください。

テストケース登録系のAPI

qUnitでテストケースを登録するAPIはtestとasyncTestの2つあり、以下の内容となります。
// 通常利用するテストケース追加API。第1引数にテスト名、第2引数にテスト内容を渡す。
test("ok test", function(){
  ok(true);
});

// 非同期通信処理などをテストする時に利用するテスト追加API。
// アサートしたい箇所の前で、start()を呼び出すとqUnitが動き出す。
asyncTest("async test", function() {
  setTimeout(function(){
    start();
    ok("1", "async Text passed!");
  }, 3000);
});
// なお、asyncTestは、testメソッドの以下の書き方と同様の動きをする。
test("async test2", function() {
  stop();
  setTimeout(function(){
    start();
    ok("1", "async Text passed!");
  }, 3000);
});


アサート系のAPI

アサートを行うAPIには、以下の種類があります。
// okメソッド。
// jUnitでいうところのassertTrueメソッドと同じ。
test("ok test", function(){
  ok(1 === 1, "number assertion");
});

// deepEqualメソッド。
// オブジェクトの中身も検証できる。
test("deep equal", function() {
  var create = function() {return {
    prop1: "1",
    prop2: 10,
    prop3: ["a", "b"],
    prop4: null,
    prop5: undefined
  };};
  var actual = create();
  var expect = create();
  deepEqual(actual, expect, "Two Objects have same values.");
});


// equalメソッド。
// 等価演算(==)でアサートする。
test("equal test", function() {
  // actual == expect
  equal("1", 1, "equal test");
});


// strictEqualメソッド。
// 厳密等価演算(===)でアサートする。
test("strict equal test", function() {
  // actual === expect
  strictEqual("1", 1, "equal test"); // may be failed.
});

// expectメソッド。
// アサートがexpectメソッドに記載した回数呼び出されたかを確認できる。
// 条件分岐に入ったかや、関数を呼び出したかなど確認するなどに利用できる。
test("expect num of test", function() {
  expect(2);
  ok(true);
  ok(true);
  if (true) {
    ok(true); // fail because over expect num.
  }
});

// throwsメソッド。
// テスト対象が例外通知をする場合のテストを行うことができる。
test( "throws", function() {

  function CustomError( message ) {this.message = message;}
  CustomError.prototype.toString = function() {return this.message;};

  throws(
    function() {throw "error"},
    "throws with just a message, no expected"
  );

  throws(
    function() {throw new CustomError();},
    CustomError,
    "raised error is an instance of CustomError"
  );

  throws(
    function() {throw new CustomError("some error description");},
    /description/,
    "raised error message contains 'description'"
  );
});



テストモジュールの分割

以下のメソッドを利用することで、テストをモジュールに分割することが出来ます。
module("moduleA");
これを記載した行以降のtest, asyncTestは、そのモジュールに属することとなります。

モジュール毎にテストケースを呼び出す事が可能で、モジュールの指定は、URLのクエリパラメータで指定します。
例えば、moduleAのみをテストする場合のURLは以下となります。
http://localhost/test.html?filter=moduleA


テストケース実施前後の処理

各テストケース実施前後で、以下のメソッド記述しておく事で、処理を行うことが可能です。
// 各テストケース実行前に呼び出される。
// jUnitでいうところのsetUpメソッド。
QUnit.testStart(function() {
  console.log("testStart is called.");
});

// 各テストケース実行後に呼び出される。
// jUnitでいうところのtearDownメソッド。
QUnit.testDone(function() {
  console.log("testDone is called.");
});


上記以外に、モジュール毎に、開始時/終了時に処理を行う事も可能です。
QUnit.moduleStart(function() {
  console.log("moduleStart is called.");
});

QUnit.moduleDone(function() {
  console.log("moduleDone is called.");
});


あと、テストケース全体で、開始時/終了時に処理を挟む事も可能です。
QUnit.begin(function() {
  console.log("begin callback is called. date=" + new Date());
});

QUnit.done(function() {
  console.log("done callback is called. date=" + new Date());
});



その他

アサートを終了時に、毎回呼び出す事ができるメソッドも用意されています。
QUnit.log(function(options) {
  console.log("log is called."
     + " result=" + options.result
     + ", actual=" + options.actual
     + ", expected=" + options.expected 
     + ", message=" + options.message);
});
これを使うとブラウザに結果を表示しなくても、コンソール出力とかが出来るので、 ヘッドレスなテスト(phantomJSなど)で利用するとメリットがありそうです。



qUnitについて、今後色々とやってみたいこと

JavaScriptの単体テストって自分の周りではあまり普及していない印象ですが、 色々と便利に/自動的に使えるように模索したいと考えています。

qUnitについて、今後以下の事を調べたいと考えています。
  • Cookbookというケーススタディのページがあるので、じっくり読んで便利な使い方を学ぶ。
  • PhantomJSを用いたヘッドレス(ブラウザなし)でのテスト方法を学ぶ。
  • 「Clickテストや、DOM操作系のテストはどうすれば良い?」を考察する。



最後に

JavaScriptの単体テストは、前々からやりたいと思っていました。
単体テストを自動化できるって、リファクタリングや機能追加を行う際に、強い心の支えとなります。
他言語では日常のようにされているメリットを、JSでも享受できる形を見つけられればと思います。

最後までご覧頂きましてありがとうございました。





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。