javascriptのテストのはなし:SugerTest

今回はマイナーであろうSugerTestを紹介します。
ユニットテスト用のライブラリで、JsUnitTestをベースとした拡張版?という感じですね。
実際に利用するにはSugarTestのライブラリと合わせてJsUnitTestのライブラリをロードする必要があります。テスト実行結果の画面はJsUnitTestの画面そのものです。

準備

ここからSugerTestのライブラリを落とします。
先に書いた通り、JsUnitTestとSugarTestのライブラリ両方を使う必要がありますが、ダウンロードしたSugerTestのライブラリの中にJsUnitTestも含まれているので、特に気にする必要はないです。
ダウンロードしたzipを解凍するとassetというディレクトリがあって、その中にJsUnitTestとSugerTestのjsが入っているのでそれをhtmlで指定します。
また、htmlのbodyの中に

<div id="testlog"></div>

を用意しておきます。JsUnitTestがこのタグに対してテスト結果を出力します。

アサーション

ドキュメントによると、使えるアサーションはJsUnitTestのアサーションで次のものが挙げられています。

assert、assertEqual、assertNotEqual、assertEnumEqual、assertEnumNotEqual、assertHashEqual、assertHashNotEqual、assertIdentical、assertNotIdentical、assertNull、assertNotNull、assertUndefined、assertNotUndefined
ちなみに、JsUnitTestには上記以外にも
assertNullOrUndefined、assertNotNullOrUndefined、assertMatch、assertNoMatch、assertHasClass、assertHidden、assertInstanceOf、assertNotInstanceOf、assertRespondsTo、assertRaise、assertNothingRaised、assertVisible、assertNotVisible、assertElementsMatch、assertElementMatches
があります。

使い方

基本

SugarTest().run();

でテストを実行します。

コンテキストを作る

describe()でコンテキストを作り、it()でテストケースを複数定義、end()でコンテキストを終えます。describe()は複数作れるし、ネストする事もできます。

SugarTest()
	.describe("context 1")
		.it("testcase 1-1", function(){...})
		.it("testcase 1-2", function(){...})
		.describe("context 1-2")
			.it("testcase 1-2-1", function(){...})
			.it("testcase 1-2-2", function(){...})
		.end()
	.end()
	.describe("context 2")
		.it("testcase 2-1", function(){...})
		.it("testcase 2-2", function(){...})
	.end()
.run();

全てのメソッドがsugarTestのオブジェクトを返すので、コンテキスト分、ユニット分、チェーンする事ができます。
チェーンさせて記述するのがSugarTestの特徴ですね。

setUpとtearDwon

SugarTestの場合は、setUp、tearDownという名称ではなく、before()、after()で定義します。これらのメソッドもsugarTestのオブジェクトを返すのでチェーンさせて記述します。

SugarTest()
	.before(function(data){...})
	.after(function(data){...})
	.describe("context 1")
		.it("testcase 1-1", function(){...})
		.it("testcase 1-2", function(){...})
	.end()
.run();

また、before()、after()はコンテキストの中でも行う事が出来ます。
before()の場合はネストの外側から内側に、after()の場合はネストの内側から外側に向けて実行されます。

SugarTest()
	.before(function(data){...})
	.after(function(data){...})
	.describe("context 1")
		.before(function(data){...})
		.after(function(data){...})
		.it("testcase 1-1", function(){...})
		.it("testcase 1-2", function(){...})
	.end()
.run();

ルートコンテキスト

ルートコンテキストの直下にもユニットを作れます。

SugarTest()
	.before(function(data){...})
	.after(function(data){...})
	.it("root testcase", function(){...})
	.describe("context 1")
		.before(function(data){...})
		.it("testcase 1-1", function(){...})
		.it("testcase 1-2", function(){...})
	.end()
.run();

root()を使うとルートコンテキストに戻れます。

SugarTest()
	.before(function(data){...})
	.after(function(data){...})
	.it("root testcase1", function(){...})
	.describe("context 1-1")
		.before(function(data){...})
		.it("testcase 1-1", function(){...})
		.it("testcase 1-2", function(){...})
	.root()
	.it("root testcase2", function(){...})
	.end()
.run();

補足

describe()/it()の代わりに、context()/should()を使う事も出来ます。テスト実行時の表示が変わるだけで基本的に同じです。

実行してみる

ライブラリに入っているサンプルのテストファイルsugar_test_sample.htmlがやれる事を網羅していて良い感じなのでこれを動かしてみます。実行結果は次の様な感じになります。
コンテキストをネストさせてテストが構造化できるわけですが、結果表示はフラットなのでいまいち分かりにくいですね。。テスト実行~結果表示まではJsUnitTestの機能なので仕方ないんでしょうけど。

ちなみにsugar_test_sample.htmlのソースはこんな感じになってます。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
          "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
	<title>SugerTest</title>
	
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	
	<link rel="stylesheet" href="assets/unittest.css" type="text/css" />
	<script src="assets/jsunittest.js" type="text/javascript"></script>
	<script src="assets/sugar_test.js" type="text/javascript"></script>
	
	<!-- Source and test files go here -->
    
</head>

<body>
<div id="content">
	<div id="header">
		<h1>SugarTest sample test file</h1>
		<p>This file shows example usage of <a href="http://sugartest.scriptia.net/">SugarTest</a>.</p>
	</div>
	<div id="testlog"></div>
</div>

<script type="text/javascript">
	// <![CDATA[
		SugarTest("AAAA")
			.it("root", function(data){
				this.assert(true);
			})
			.before(function(data){
				this.wadus = "wadus";
			})
			.after(function(){
				this.wadus = "";
			})
			.describe('First context')
				.it('runs examples', function(data) {
					this.assert(false);
				})
				.it('runs examples2', function(data) {
					this.assert(true);
				})
			.end()
			.describe('Second context')
				.before(function(data){
					this.wadus = this.wadus.toUpperCase();
				})
				.it('runs examples', function(data) {
					this.assertEqual(this.wadus, "WADUS");
				})
			.end()
			.describe('Third context')
				.should('runs examples', function(data) {
					this.assertEqual(this.wadus, data.wadus);
				})
			.end()
		.run();
	// ]]>
	</script>
</body>
</html>