JUnit ソースリーディング中...(3)

さすがに理解はできてきたかな。「使える」ようになったかはともかく。

ちょっと整理してみる。自分のために。

JUnitはテキストベースでも使えるし、GUI は awt と swing が使える。それぞれ、

が起動クラスである。つまりこいつらの main が最初に呼び出されることでテストが開始するのだ。これらは junit.runner.BaseTestRunner の子クラスにあたる。

簡単そうな、junit.textui.TestRunner から読むことにした。main の引数にテストクラスをFQCNで指定することで、そのテストクラスが実行され、結果がコンソールに出力されることになる。基本的にこれだけなので、わかりやすい。

ほかの主役はみな、junit.framework パッケージ以下にいる。

  • TestCase
  • TestSuite

これらは Composite パターンで作られている。つまりファイルとフォルダの関係である。それぞれ Test インターフェースを実装していて run メソッドを持つことが義務づけられている。もし Test の実態が TestCase なら実際にテストメソッドが呼び出されるし、TestSuite ならこいつが持っている TestCase を順に実行していくということになる。

テストクラスは TestCase を extends して書くことからもわかるとおり、これらはテストそのものを管理しているクラスだ。役割としては「実行されたら、setup->テストメソッド->tearDown の順に実行する(ちなみにここは TemplateMethod パターンが適用されている)。例外やエラーが発生したら上に投げる」ということをしてさえいればよろしい。失敗したからどう成功したからどうとかは考えない。「結果をどうユーザに伝えるかという表現」は別のクラスに任せている。

次に、

  • TestResult

こいつはテスト結果収集屋さんである。Kent Beck に言わせれば「Collecting Parameter」である。収集というくらいなので、実行する際の全てのテストの結果を保持することになる。TestRunner からテスト実行される際に、ひとつ new して作られて、Test オブジェクト(TestCase とか TestSuite とか)の run メソッドに引数として渡されている。このインスタンスが収集することになる。一回の起動ではTestResultのインスタンスは1つだけである。

こいつの役割はあくまでテスト結果の収集だ。テストが何回実行されて、そのうち失敗(assert 失敗)が何件で、エラーが何件かを管理している。

TestRunner クラスの中では doRun の中で

  suite.run(result);

してるかと思えば、呼ばれた TestCase クラスの run メソッドの中では、

  result.run(this);

と実行の主体がスイッチしてしまっている。
なんとも難解であるが、TestResultの別の役割を理解すれば納得できる。

TestResult はテスト結果を管理する以外にも「テスト状況を TestListener に伝える」という役割も持っており「テスト開始」、「テスト結果」、「テスト終了」をそのタイミングでTestListenerに通知している。逆に言うとこれらを通知しようと思うと、こいつがTestCaseを実行するしかないということである。

この「通知」というところを、掘り下げてみる。

  • TestListener

TestListener インタフェースの中身は以下のとおりである。

package junit.framework;

/**
 * A Listener for test progress
 */
public interface TestListener {
	/**
 	 * An error occurred.
 	 */
	public void addError(Test test, Throwable t);
	/**
 	 * A failure occurred.
 	 */
 	public void addFailure(Test test, AssertionFailedError t);  
	/**
	 * A test ended.
	 */
 	public void endTest(Test test); 
	/**
	 * A test started.
	 */
	public void startTest(Test test);
}

TestResult が TestCase を実行する際に、

    • テスト呼ぶ直前には、Listener の startTest をよび、
    • テストが失敗(assert失敗)したら、Listener のaddFailure をよび、
    • テストが例外をおこしたら、Listener の addError をよび、
    • テストが終わったら、Listener の endTest をよぶ

ということをしているのだ。いわゆるObserverパターンである。

実は BaseTestRunner も TestListener を実装しているのだが、textui ではあまり気にしなくていい。textui の場合、TextResult に addListener されているのは、textui パッケージにある、ResultPrinter である。

TestRunnerの中で、上記コードの

suite.run(result); 

の直前に、

result.addListener(fPrinter); 

と、ResultPrinter がリスナーとして追加されている。

こいつが結果をこまかく受け取って「結果出力」する本体である。textui はその名の通り GUI がないので、コンソールに出力している。その出力の主体である。もっというとその中身は System.out である。

実は、肝はこれで終わりかなと。

あとは出力先が、awt になったり、swing になったりと。

その気になれば、Listener を実装したこだわりのクラスを作りさえすれば、テスト開始や終了、エラー発生の通知を受けて、音を出したり、ランプをつけたりもできるでしょうってことでしょう。たぶん。


さて、つぎは awtui.TestRunner でも見てみますかのう。