JsTester(http://jstester.sourceforge.net/) は、JUnit3/4 やTestNG といったテストツールと連携してJavaScriptによるスクリプトのテストを行うツールです。執筆時点で最新のバージョン1.4では、JDK6のjavax.scriptパッケージ(JSR223) をサポートしました。配布ライセンスはApache License Version 2.0で、ダウンロードモジュールはJDK1.4、同5、同6に対応したバイナリとソースが用意されています。
Ajaxが注目されるようになり、JavaScriptを用いたWebアプリケーションを構築する機会が増えていますが、開発工程で問題になるのが、それらをテストする方法です。Javaプログラムはアプリケーションサーバ上で動作しますが、JavaScriptによるスクリプトはWebブラウザ上で動作します。そのため、動作を確認するテストはそれぞれの環境で行う必要があります。
Javaであれば、前述したJUnitやTestNGといったテストツールが普及しています。これらはEclipse 、NetBeans といったIDE(統合開発環境)で実行することができます。JavaScriptのテストツールとしては、WebブラウザのFirefox上で動作するSelenium(http://www.openqa.org/selenium/) がよく知られています。
なぜJsTesterなのか
ただ、Webブラウザ上で実行する前にJavaScriptのスクリプトをテストしておきたい場合もあるでしょう。たとえば、以下のような場合です。
Javaプログラムで生成するJavaScriptのソースコードをテストしたい
Webブラウザ画面の動作とは直接関係のないサーバとの連携部分をテストする
開発マシンはテキストインターフェースで利用していて、JavaScriptに対応したWebブラウザを実行できない
そもそも、テストするプログラムごとにテスト環境を切り替えるのは面倒
など....
思い当たるところがあれば、JsTesterを試してみてはいかがでしょうか。これは一般的なJavaツールなので、JUnitやTestNGに組み込んでテストを実行できます。ですから、テストごとに実行環境を切り替える必要がなく、JavaとJavaScriptとの連携も一般的なアプリケーションと同じようにテストできます。
たとえばJavaプログラムで生成したスクリプトをテストする場合、テスト環境がばらばらだと、生成したスクリプトを一旦ファイルに保存して、テスト環境を切り替えてからJavaScriptをテストするというような作業の流れになるでしょう。
しかしJsTesterであれば、テストツール内でスクリプトを生成するプログラムを実行し、生成されたスクリプトをファイルに保存せずにそのままJsTesterに読み込ませて、引き続きテストすることができます。テスト環境を切り替えずに済むことで、生成されたスクリプトをファイルを保存する必要もなくなり、作業効率の向上が期待できます。
JavaScriptのテスト方法
JsTesterを利用してJavaScriptのスクリプトをテストする方法には、大きく2通りあります。
独自に拡張したTestCaseを用いる方法
JsTesterクラスを用いる方法
a) 独自に拡張したTestCaseを用いる方法
JUnitのTestCaseクラスを拡張したJsTestCaseクラス(JDK6の場合はJDK6JsTestCaseクラス)を用いてテストを行う方法です。この方法は、JUnitユーザには馴染みやすく、テストケースのソースコードの記述が少なくて済むメリットがあります。一方で、JUnit上でしか利用できないというデメリットもあります。
JDK6対応のJDK6JsTestCaseを用いる場合の注意
JDK6JsTestCaseにはバグがあり、loadScript()メソッドを実行すると例外がスローされます。このクラスのソースコードの314行目を以下のように変更すると、この問題を解決できます。これは今後改善される可能性があります。
変更前)
return JsTester.loadScript( scriptName );
変更後)
return jsTester.loadScript( scriptName );
b) JsTesterクラスを用いる方法
JsTesterクラス(JDK6の場合はJDK6JsTesterクラス)は、JavaScriptのテストを行う直接行うクラスで、スクリプトを読み込んで実行し、結果を判定します。判定時に実行するメソッドには、assertEquals() のようにJUnitでは馴染み深いものだけでなく、テスト対象がJavaScriptだからこそ判定する必要がある独自のメソッドまで用意されています。
この方法はJUnitで一般的なTestCase内でも実行できますし、TestCaseを必要としないJUnit4やTestNGにも対応できるのがメリットです。デメリットとしては、テストケース内にJsTesterオブジェクトを用意しなくてはならず、処理がいくらか複雑になることがあります。
テストケースの作成(その1)-JUnit 3.8対応
それでは、実際にJsTesterを用いてスクリプトのテストを行うテストケースを作成してみましょう。まずはJUnit 3.8 対応の場合です。
JUnit3.8では、TestCaseクラスを拡張してテストケースを作成します。そしてこれを実行するTestRunner には、テキストインターフェースのものとGUIインターフェースのものがあります。ここではJsTesterで独自に拡張されたTestCaseでテストケースを作成し、GUIインターフェースのTestRunnerでそれを実行することにします。
テスト対象となるスクリプトをリスト1 に示します。ここで定義された関数func1と、Example、Calculatorという2つのオブジェクトがテストされます。
リスト1 テスト対象のスクリプト(test.js)
function func1( fp1 ) { // テスト対象(1)
return fp1;
}
function Example() {
Example.prototype.add = function(a, b) { return a + b; }
}
exmp = new Example(); // テスト対象(2)
function Calculator() {
Calculator.prototype.add = function(a, b) { return a + b; }
Calculator.prototype.subtract = function(a, b) { return a - b; }
Calculator.prototype.multiply = function(a, b) { return a * b; }
Calculator.prototype.divide = function(a, b) { return a / b; }
}
objCalc = new Calculator(); // テスト対象(3)
作成したテストケースをリスト2 に示します。これは独自に拡張されたJsTestCaseクラスを継承しています。
JsTestCaseクラスには、テスト対象がJavaaScriptだからこそ必要となるassertメソッドがいろいろと用意されています。リスト2では、その一部を用いてテストを行っています。以下にテスト中に実行するメソッドを示します。テスト用のメソッドは5つで、最後の(5) は実行結果が数値であって文字列ではないので、テストは失敗するようになっています。
(1) assertIsFunction 関数であるかどうか
(2) assertIsObject オブジェクトであるかどうか
(3) assertExpressionEquals 式の結果と答えとが等しいか
(4) assertIsEmpty オブジェクトは空であるか
(5) assertIsString オブジェクトの内容は文字列か(リスト2では失敗)
テストするスクリプトはリスト1のみなので、setUp() メソッドで読み込んだスクリプトをずっと利用していますが、コメントアウトしている通り、テストを行うメソッドのそれぞれでもスクリプトを読み込むことは可能です。
リスト2 JUnit3.8に対応したテストケース(JsTestCase版の例)
import net.sf.jstester.JsTestCase;
public class JsTesterJUnit38Test1 extends JsTestCase {
// (1) func1は関数かどうか
public void testFunction() {
// eval( loadScript( "test.js" ) );
assertIsFunction( "関数ですか?", "func1" );
}
// (2) exmpはオブジェクトかどうか
public void testObject() {
// eval( loadScript( "test.js" ) );
assertIsObject( "オブジェクトですか?", "exmp" );
}
// (3) exmpのadd関数はうまく実行できるか
public void testExample() {
// eval( loadScript( "test.js" ) );
assertExpressionEquals( "exmp.add(3,2)", "5" );
}
// (4) objCalcは空かどうか
public void testEmpty() {
// eval( loadScript( "test.js" ) );
assertIsEmpty( "空ですか?", "objCalc" );
}
// (5) objCalcのdivide関数の実行結果は文字列か - failure!!
public void testCalcDiv() {
// eval( loadScript( "test.js" ) );
assertIsString( "objCalc.divide(6,3)" );
}
public void setUp() throws Exception {
super.setUp();
eval( loadScript( "test.js" ) );
}
}
リスト2の実行結果を図1 に示します。実行するには、JDK1.4以上、JsTester、JUnitのほかに、Commons Logging 、Rhino のJARファイルをクラスパス(CLASSPATH)に追加する必要があります。
図1 リスト2の実行結果
JavaScriptのテストは必ずJsTestCaseで作成しなければならないことはありません。テストを行うJsTesterオブジェクトを用意すれば、一般的なTestCaseクラスを継承してテストケースを作成することもできます。リスト3 にその例を示します。実行結果はリスト2と同様になります。
リスト3 JUnit3.8に対応したテストケース(TestCase版の例)
import junit.framework.TestCase;
import net.sf.jstester.JsTester;
public class JsTesterJUnit38Test2 extends TestCase {
private JsTester jsTester;
public void testFunction() {
jsTester.assertIsFunction( "関数ですか?", "func1" );
}
public void testObject() {
jsTester.assertIsObject( "オブジェクトですか?", "exmp" );
}
public void testExample() {
jsTester.assertEquals( "exmp.add(3,2)", "5" );
}
public void testEmpty() {
jsTester.assertIsEmpty( "空ですか?", "objCalc" );
}
public void testCalcDiv() {
jsTester.assertIsString( "objCalc.divide(6,3)" );
}
public void setUp() throws Exception {
jsTester = new JsTester();
jsTester.onSetUp();
jsTester.eval( JsTester.loadScript( "test.js" ) );
}
public void tearDown() throws Exception {
jsTester.onTearDown();
}
}
このようにしてテストケースを作成する場合は、必ずJsTester#onSetUp() メソッドとJsTester#onTearDown() メソッドを、テストケースのsetUp()、tearDown()の各メソッドで実行しなければなりません。また、JsTestCaseでは式の結果をテストするためにassertExpressionEquals()メソッドを実行しましたが、JsTesterの場合は、同じ目的のためにassertEquals() メソッドを実行しなくてはならないということです。他のメソッドはそのままでも良いのですが、このメソッドは名称が分かれていますので、コーディング時に注意してください。
テストケースの作成(その2)-JUnit 4.3対応
次にJUnit4.3 に対応したテストケースを作成してみます。JUnit4では、テストを行うメソッドに@Test 、テスト前の処理を行うメソッドに@Before 、テスト後の処理を行うメソッドに@After というアノテーションを付記することになっています。ですから、JDK5以上の環境でなければ実行することはできません。これによりTestCaseを継承する必要がなくなりました。そして、GUIインターフェースのTestRunnerは存在しません。EclipseなどでJUnitのテストケースを実行できるようになりましたので、サポートする必要がなくなったと判断されたのでしょう。
リスト4 にテストケースの例を示します。テストの内容はリスト2と同様です。実行結果では、testCalcDivメソッドによるテストが失敗していることがわかります。実行にはJDK5、JUnit4.3が必要です。そしてリスト2の場合同様にCommons LoggingとMozilla RhinoのJARファイルをクラスパスに追加しておきます。
リスト4 JUnit4.3対応のテストケース(例)
import net.sf.jstester.JsTester;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class JsTesterJUnit43Test {
private JsTester jsTester;
@Test
public void assertIsFunction() {
jsTester.assertIsFunction( "関数ですか?", "func1" );
}
@Test
public void assertIsObject() {
jsTester.assertIsObject( "オブジェクトですか?", "exmp" );
}
@Test
public void testExample() {
jsTester.assertEquals( "exmp.add(3,2)", "5" );
}
@Test
public void assertIsEmpty() {
jsTester.assertIsEmpty( "空ですか?", "objCalc" );
}
@Test
public void testCalcDiv() {
jsTester.assertIsString( "objCalc.divide(6,3)" );
}
@Before
public void setUp() throws Exception {
jsTester = new JsTester();
jsTester.onSetUp();
jsTester.eval( JsTester.loadScript( "test.js" ) );
}
@After
public void tearDown() throws Exception {
jsTester.onTearDown();
}
}
図2 リスト4の実行結果
JUnit version 4.3.1
.....E
Time: 1.602
There was 1 failure:
1) testCalcDiv(JsTesterJUnit43Test)
net.sf.jstester.util.JUnit4JsAssertionError:
.......... エラーメッセージ ..........
FAILURES!!!
Tests run: 5, Failures: 1
テストケースの作成(その3)-TestNG5.6対応
最後に、TestNG5.6 に対応したテストケースを作成します。TestNGはJUnit4と同様に、メソッドにアノテーションを付記することでメソッドの役割を認識します。テストを行うメソッドは@Test 、テストの前処理を行うメソッドは@BeforeMethod 、テストの後処理を行うメソッドは@AfterMethod です。
リスト5 にテストケースの例を示します。ここでも@BeforeMethodを付記したメソッドではjsTester#onSetUp()、@AfterMethodを付記したメソッドではjsTester#onTearDown()の各メソッドを実行しています。実行結果はコンソールにも表示されるほか、HTMLファイルも生成されますので、図4 のようにWebブラウザで閲覧することができます。
リスト5 TestNG5.6対応のテストケース(例)
import net.sf.jstester.JsTester;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class JsTesterTestNG56Test {
private JsTester jsTester;
@Test
public void assertIsFunction() {
jsTester.assertIsFunction( "関数ですか?", "func2" );
}
@Test
public void assertIsObject() {
jsTester.assertIsObject( "オブジェクトですか?", "exmp" );
}
@Test
public void testExample() {
jsTester.assertEquals( "exmp.add(3,2)", "5" );
}
@Test
public void assertIsEmpty() {
jsTester.assertIsEmpty( "空ですか?", "objCalc" );
}
@Test
public void testCalcDiv() {
jsTester.assertIsString( "objCalc.divide(6,3)" );
}
@BeforeMethod
protected void init() throws Exception {
jsTester = new JsTester();
jsTester.onSetUp();
jsTester.eval( JsTester.loadScript( "test.js" ) );
}
@AfterMethod
protected void finish() throws Exception {
jsTester.onTearDown();
}
}
図3 リスト5の実行結果
[Parser] Running:
Command line suite
===============================================
Command line suite
Total tests run: 5, Failures: 1, Skips: 0
===============================================
図4 リスト5の実行結果(HTML)
JsTesterでは、このようにテストツールと連携してJavaScriptによるスクリプトのテストが可能です。これからますます機会が増えるであろうAjaxアプリケーション開発の場で活躍することでしょう。