test for asynchronous code with QUnit

以前のエントリで書きましたが、SQL-based database APIの一部のメソッドの実行は非同期となります。非同期のメソッドをテストする為に、QUnitを使ってみました。

QUnit

QUnitは、jQuery自体のコードやプラグインのテストに使われている、JavaScript用のテストフレームワークです。jQueryを使っていないJavaScriptや、サーバサイドJavaScriptもテスト可能とのこと。

QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery project to test its code and plugins but is capable of testing any generic JavaScript code (and even capable of testing JavaScript code on the server-side).

http://docs.jquery.com/QUnit

そして、QUnitは非同期で実行されるコードをテストする為の仕組みを提供しています。

QUnit is similar to other unit testing frameworks like JUnit, but makes use of the features JavaScript provides and helps with testing code in the browser, eg. with it's stop/start facilities for testing asynchronous code.

http://docs.jquery.com/QUnit
準備

テストを実行する為のHTMLを用意します。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <script src="http://code.jquery.com/jquery-latest.js"></script>
  <link rel="stylesheet" href="http://github.com/jquery/qunit/raw/master/qunit/qunit.css" type="text/css" media="screen" />
<script type="text/javascript" src="http://github.com/jquery/qunit/raw/master/qunit/qunit.js"></script>
<script type="text/javascript" src="./js/db.js"></script>

  <script>
  $(document).ready(function(){
    
module("Module Connection");

test("db test1", function() {
	ok( getDbConnection(), "getDbConnection()");
});

  });

function fail(msg){
	ok(false, msg);
}
  </script>
  
</head>
<body>
  <h1 id="qunit-header">QUnit example</h1>
 <h2 id="qunit-banner"></h2>
 <h2 id="qunit-userAgent"></h2>
 <ol id="qunit-tests"></ol>
 <div id="qunit-fixture">test markup, will be hidden</div>
</body>
</html>

テスト対象となるJavaScriptのコードを用意します。

function getDbConnection(){
	return openDatabase('lifecycle1', '1.0', 'lifecycle1', 5*1024);
}

function startTransaction(executeSql, error, success){
	getDbConnection().transaction(executeSql, error, success);
}
テストコード

用意したHTMLの中で、$(document).readyの中にテストコードを書いていきます。テストメソッドは、test関数を用いて実行します。テストメソッドのグルーピングは、moduleという要素を使って行います。JUnitで言うassert*等のメソッドに該当する「Assertions」は、ok、equals、sameの3つしか用意されていません。このあたりの詳細については、API documentationを参照してください。

module("Module Connection");

test("db test1", function() {
	ok( getDbConnection(), "getDbConnection()");
});
実行

ブラウザでHTMLファイルを開くとテストを実行し、結果を表示します。
テストが成功すれば、以下のようにgreenになります。

失敗すれば、以下のようにredになります。

非同期コードのテスト

SQL-based database APIを使用し、非同期のコードをテストします。

test("db test2_sync", function() {
	startTransaction( // 以下、非同期で実行される関数
		function(t){
			t.executeSql('CREATE TABLE IF NOT EXISTS for_tests(id INTEGER PRIMARY KEY, note TEXT)');
			t.executeSql("SELECT * FROM for_tests", [], function(tx, rx){});
		},
		function(e){
			fail("must not failed.");
		},
		function(){
			ok(true, "success to access for_tests.");
		}
	);
});

function fail(msg){
	ok(false, msg);
}

このテストコードを実行すると、以下のようになります。

テストが実施されていません。テストコードのAssertionsの呼び出しはtest関数のコンテキストに対して非同期に実行されますが、test関数はその非同期処理の終了を待たずに終わってしまう為、テスト結果が反映されません。
そこで、test関数ではなく、asyncTest関数を使ってテストします。

asyncTest("db test2_async", 1, function() {
	startTransaction(
		function(t){
			t.executeSql('CREATE TABLE IF NOT EXISTS for_tests(id INTEGER PRIMARY KEY, note TEXT)');
			t.executeSql("SELECT * FROM for_tests", [], function(tx, rx){});
		},
		function(e){
			fail("must not failed.");
			start();
		},
		function(){
			ok(true, "success to access for_tests.");
			start();
		}
	);
});

test関数→asyncTest関数に切り替え、テスト完了後にstart関数を呼び出すようにしています。start関数を呼び出さないとテストが完了しないので、注意が必要です。
このテストコードを実行すると、

非同期で実行されたAssertionsの結果が反映されます。
上記はSQLの実行が正常に終了するケースをテストしました。SQLの実行が失敗するケースをテストしたい場合は、以下のようになります。

asyncTest("db test3", 1, function() {
	startTransaction(
		function(t){
			t.executeSql("SELECT * FROM dummy", [], function(tx, rx){});
		},
		function(e){
			ok(e, e.message);
			start();
		},
		function(){
			fail("must not succeed.");
			start();
		}
	);
});
まとめ

今回はQUnitを使って非同期で実行される関数(SQL-based database API)のテストコードを記述しテストしました。Web Workersについても、同じような形式でテストできると思います。