JUnitテストの実行環境をバージョンアップする時の落とし穴 #tddadventjp

これはTDD Advent Calendar jp: 2012参加記事です。

前日(8日目)は、KTZさんの「Rhino.Mocksをちょっとだけ幸せにするお助けクラス」でした。

xUnitによるテスティングフレームワークの共通仕様として、「テストクラス内のテストの実行順序は不定」というのがあります。

とはいえこの仕様をテストを書く上で意識することはあまりありません。テストのあるべき姿として、テストメソッドは他のメソッドから独立しているべきですし、JUnitの場合、ほとんどの実行環境上で、ソースコード上の並びと同一順でテストが実行されていたからです。

しかしJava7(Oracle実装)からは事情が異なります。

package jp.fieldnotes.java;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

public class OrderTest {

	@Rule
	public TestName name = new TestName();
	
	@Test
	public void インスタンスから値を取得できること() {
		System.out.println(name.getMethodName());
	}

	@Test
	public void 引数にnullを渡した時にnullが返ること() {
		System.out.println(name.getMethodName());

	}

	@Test
	public void キーが重複している場合に例外になること() throws Exception {
		System.out.println(name.getMethodName());
	}
}

上記のようなテストケースをJUnit4.10以前で実行した場合*1、java6で実行した場合は

インスタンスから値を取得できること
引数にnullを渡した時にnullが返ること
キーが重複している場合に例外になること

という順番となりますが、Java7で実行した場合は

引数にnullを渡した時にnullが返ること
インスタンスから値を取得できること
キーが重複している場合に例外になること

という結果となります。

これはJUnitがテストメソッドの一覧を取得しているのに使用している java.lang.class#getDeclaredMethods() が、Java6以前とJava7で異なった順番で結果を返すためです。この結果に引きずられて、テストの実行順序もかわってくるわけです。

前述しているように、テストメソッド相互の独立性を確保していればこの問題にひっかかることはないのですが、static領域の書き換えを行っているなどでテストメソッド同士が暗黙に依存してしまっている場合は、この問題がひっかかります。

...という問題をJUnitのメンテナも認識していたようで、JUnit4.11からはテストの実行順序に関する仕様がかわりました。

JUnit4.11からは、テストメソッドは、実行環境に関係なく、テストメソッドの名称のハッシュコードの昇順で実行されるのが仕様となりました。

もし今まで通りの順番で実行したい場合は、テストクラスに@FixMethodOrder(MethodSorters.JVM) のアノテーションを付与することにより、これまでと同じ順番で実行されます。この他にもテストメソッドの名前でソートして実行される@FixMethodOrder(MethodSorters.NAME_ASCENDING) のアノテーションが用意されています。

前述した通り、テストメソッド相互が独立しているような、あるべき姿でテストを書いていれば、この問題にひっかかることは少ないと思います。しかしながら、一定規模のテストケースがある状態で、JVMをバージョンアップする場合や、JUnitをバージョンアップする場合は、要注意のポイントとなるでしょう。

*1:WindowsXP SP3+ Java6 u31/ Java7 u3です。Javaが古いのは作者が調査してから公開するのをサボっていたためです^^;