Release 2.0.0
18 April 2001
日本語訳
masaki@metabolics.co.jp
2001/4/26
テスト・データはテストにおいて中心的な役割を果たすものだ。最も低いレベルのテスト(メソッド・テスト)から最も高いレベルのテスト(検収テスト)まで、入力データ、出力されるはずのデータ、出力されるはずのデータと実際の出力データの比較がテストにはついて回る。
テスト・コードからテスト・データを分離することにはいくつかの利点がある:
JXUnitはディレクトリ・ベースのテスト記述システムで、JUnitの上に構築されている。JXUnitの操作は以下の三つの段階からなる:
これはJXUnitの最初の製品リリースである。インストールを簡単にするためにパッケージを変更した(必要なjarファイルはすべて入れるようにした)。
新しいフィーチャ:
jxu.qimlファイルとjxuc.qimlファイルを、JXUnitのjarファイルに追加した。なので、いちいちテスト・ディレクトリにこれらのファイルをコピーする必要はなくなった。
さらにいくつかのドキュメントをQuick Wiki Wikiに追加した(Wiki Wikiとは見た人が変更することのできるwebページの集まりのこと) 。
テストは以下の小さなステップに分けて考えることができる:
テストの属性とはテストのステップ間で受け渡されるデータのことで、属性ごとに名前と値がある。
さて、最初の例を見れば我々がやろうとしていることがだいたい分かってもらえるのではないかと思う:
<jxu> <set name="input" value="123"/> <eval stepClass="DummyTestStep"/> <isEqual name="output" value="123" message="Output was not 123"/> </jxu>
ここでは、Java XML Unit(JXU)というXMLを使ってテストのステップの並びを記述している:
クラスnet.sourceforge.jxunit.JXTestCaseはJXUnitのフレームワーク全体を提供する。このクラスはjunit.framework.TestCaseをextendしたもので、JUnitから呼ばれるsuitクラスを持っている。
次のクラスnet.sourceforge.jxunit.JXPropertiesは、テストの属性のコンテナ・クラスである。このクラスはjava.util.HashMapをextendしている。
上の例のようなJXUテスト・スクリプトはオブジェクトの集合に変換される。上記のset, eval, isEqualに使われているのは以下のクラスである:
| set | net.sourceforge.jxunit.JXTestSet | eval | net.sourceforge.jxunit.JXEval | isEqual | net.sourceforge.jxunit.JXIsEqual |
package net.sourceforge.jxunit;
public interface JXTestStep
{
public void eval(JXTestCase testCase)
throws Throwable;
}
ではDummyTestStepのコードを見てみよう:
import net.sourceforge.jxunit.*;
public class DummyTestStep implements JXTestStep
{
public void eval(JXTestCase testCase)
throws Throwable
{
JXProperties properties=testCase.getProperties();
Object data=properties.get("input");
properties.put("output",data);
}
}
上記コードのJXTestCaseで使われているgetPropertiesメソッドに注意してほしい。このメソッドで、テストのステップ間で引き渡されるデータへのアクセスを行っている。
JXUマークアップ言語は、テスト・ステップの並びからテストを生成するのに用いられる。JXUマークアップ言語とJXUnitクラスの関係は、QJMLドキュメントに記述している。例えば以下のQJMLの一部には、eval要素およびJXEvalクラスとの関係を記述している:
<bean tag="eval"> <rem>Create and run a test step.</rem> <implements>testStep</implements> <targetClass>net.sourceforge.jxunit.JXEval</targetClass> <attributes> <item coin="stepClass"> <field name="stepClass"/> </item> </attributes> </bean> <text tag="stepClass"> <rem>The fully qualified class name of the test step</rem> </text>
これを表形式で書くとすると次のようになる:
| element name |
|
|||||||
| eval |
|
(ここにJXUのすべてを表した表と、そのJavaでの実装方法が書いてある)
JXUを拡張して、自分のクラスを入れるのは簡単である(ただしJXTestStepをimplementしている必要がある)。そのやり方は:
JXUスクリプトの中で相対パス名が使われている場合には、その基点はいつも、アクティブな(=今使っている)test.jxuファイルのあるディレクトリになる。以下は例:
<jxu> <set name="input" file="myData.txt"/> <eval stepClass="DummyTestStep"/> <isEqual name="output" file="myData.txt" message="Dummy Test Failure: output does not match myData.txt"/> </jxu>
単体テストを生成するときには、たいていテスト・オブジェクトを生成してメソッドやテスト中のサブ・システムに渡すものだ。いよいよ、テキストの文字列を適当なオブジェクトに変換して、テスト・ステップのクラス群を生成する番になった。しかしこのままではオブジェクトの複雑さが増すにつれて、無様なやり方となってしまう。
そこで複雑なデータ構造を表すためにXMLを使うことにする。JXUnitではこの構造をテスト・オブジェクトに変換することができる。例を見てみよう:
<dataList> <dataItem>abc<dataItem> <dataItem>def<dataItem> <dataItem>g<dataItem> <dataItem>hi<dataItem> </dataList>
ここでやりたいことは、この構造をStringオブジェクトのListに変換することだ。XMLからJavaへのマッピングは以下のようになる:
| dataList |
|
|||
| dataItem |
|
下がこのマッピングを定義しているQJMLファイルだ:
<qjml root="dataList"> <bean tag="dataList"> <rem>A list of String objects</rem> <targetClass>java.util.ArrayList</targetClass> <elements> <item coin="dataItem" repeating="true"> <identity kind="list"/> </item> </elements> </bean> <text tag="dataItem"> <rem>A simple text value</rem> </text> </qjml>
前と同様、Quick4ユーティリティqjml2qimlを使って、QJMLファイルをちゃんと使える形式に変換する。出力ファイルの名前はmySchema.qimlとする。このファイルを使うことによって、XMLファイルからテスト・オブジェクトを生成することができる。
<jxu> <set name="input" file="myData.xml" schema="mySchema.qiml"/> <eval stepClass="DummyTestStep"/> <isEqual name="output" schema="mySchema.qiml" file="myData.xml" message="Dummy Test Failure: myData.xml"/> </jxu>
実際に上のテストを走らせてみたら、おやまぁ何と言うことか、失敗してしまったかも知れない。その理由は、myData.xmlの中身とXMLに変換されたdataListが完全に一致しなければならないからだ。これを簡単に修正して動くようにするには:
<jxu> <set name="input" file="myData.xml" schema="mySchema.qiml"/> <save name="input" file="genData.xml" schema="mySchema.qiml"/> <eval stepClass="DummyTestStep"/> <isEqual name="output" schema="mySchema.qiml" file="genData.xml" message="Dummy Test Failure: genData.xml"/> </jxu>
ここで、新たにsaveステップを追加した。saveは、入力オブジェクトからXMLファイルを生成する。こうすればisEqualはちゃんと動くはず!
テストが失敗したときには、データのコピーを保存しておくとよい。データを人間に読めるような形式で保存しておくともっとよいはずだ。以下のように:
<jxu> <set name="input" file="myData.xml" schema="mySchema.qiml"/> <eval stepClass="DummyTestStep"/> <ifEqual converse="true" name="output" schema="mySchema.qiml" file="myData.xml"> <save name="output" schema="mySchema.qiml" file="myData_.xml"/> <fail>Dummy Test Failure: myData.xml</fail> </ifEqual> </jxu>
一つのテストを走らせるだけでは不十分ということはよくある。テストを何回も、複数のスレッドで、あるいは複数のテスト・ファイルで走らせたいと思うことがあるだろう。これをテストのコンテキストと呼ぶことにしよう。テストのコンテキストはtest.jxucファイルで定義する(.jxucのcはcontextのc)。このファイルは、アクティブなtest.jxuファイルがあるのと同じディレクトリに置いておかなければならない。
上で述べたように、ここでやりたいことはいっぱいある。まずは、複数のテスト・ファイルを使えるようにしよう。何と言ってもこれがデータ中心テストの核心だからだ。(ここに現在サポートしているJXUC要素を記述した表がある。)では例題のJXUCドキュメントを見てみると:
<jxuc> <directoryScan dir="testDirectory"> <includeFiles regexp=".txt$"/> <excludeFiles regexp="_.txt$"/> </directoryScan> </jxuc>
DirectoryScanにはディレクトリからファイルを選択するオプションがある。任意個のincludeFiles要素とexcludeFiles要素を置くことができる(includeFilesがなければ、デフォルトで選択されたディレクトリのすべてのファイルを選択する)。
Regular Expressionsを使い、ファイル名からファイルをフィルタリングする。上の例では、.txtで終わるすべてのファイル、ただし_.txtで終わるもの以外を選択している。
test.jxuで定義したテストは、選択されたファイルごとに一回ずつ走る。テストの名前としては選択されたファイルの絶対パス名が使われ、absDataFileNameという名前の属性としてテスト・ステップに渡される。単純なファイル名(パスを含まないもの)もdataFileNameという名前の属性に渡される。複数のテスト・ファイルを使う場合のtest.jxuファイルの例:
<jxu> <set name="data" file="absDataFileName" indirect="true"/> <ifEqual converse="true" name="data" value="dataFileName" indirect="true"> <save name="data" file="badFileName_.txt"/> <fail>Each test file must contain only its own name!</fail> </ifEqual> </jxu>
JXUCの要素は以下のようにJavaのクラスにマッピングされる:
| directoryScan | net.sourceforge.jxunit.JXDirectoryScan | includeFiles | net.sourceforge.jxunit.JXIncludeFiles | excludeFiles | net.sourceforge.jxunit.JXExcludeFiles |
テスト・コンテキストの定義に使用されるクラスはすべて、JXTestSetupインタフェースをimplementしなければならない:
package net.sourceforge.jxunit;
public interface JXTestSetup
{
public void setup(String cwd, JXProperties properties)
throws Throwable;
}
テスト・コンテキストのセットアップがいったん完了したら、candidateFilesという名前の属性にファイル名のリストが(nullでなければ)入っているはず。テストは、このリストに入っている名前のファイルに対してそれぞれ行われることになる。各テストは、セットアップ中に初期化されたそれぞれのJXPropertiesオブジェクトのコピーを使って行われる。
JXUと同じく、JXUCにも自分のクラスを入れて拡張することができる。そのためにはJXTestSetupをimplementする:
必要なものはすべてダウンロードしたファイルに入っている。このファイルをどこでunzipしたかによって、setup.batファイルを変更する必要があるかも知れない。
JXUnitを使う前には毎回setupを走らせよう。PATHとCLASSPATH変数を変更してくれる。
テストの走らせ方はそのまんま:
java junit.textui.TestRunner net.sourceforge.jxunit.JXTestCase
上記のコマンド・ラインはどのディレクトリからでも使える。JXTestCaseはカレント・ディレクトリとその下のすべてのサブ・ディレクトリからtest.jxuファイルを探す。見つかったファイルがそれぞれテスト・ケースとなり、そのディレクトリの名前がテストの名前となる。どのテストを走らせるかは、ディレクトリを変えるだけで完全に切り替えることができる。