Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

例外発生テストメソッドに見るテスティングフレームワークの進化

テストにおいては、正しく例外が発生するかもテストする必要がある。
JUnit3の時代は、僕の周りではそのテストの書き方を理解している人が少なかった印象がある。
JUnit3では例外発生テストはこのように書くものだった。

import junit.framework.TestCase;

public class TestCaseSample extends TestCase {

	public void testExpectedException() {

		Sample s = new Sample();
		try {
			s.throwException();
			fail("Exception is not thrown.");
		} catch (IllegalStateException expected) {
			assertTrue(true);
		}
	}

	private static class Sample {
		public void throwException() {
			throw new IllegalStateException();
		}
	}
}

ポイントは2つあった。

  • 例外が発生するメソッドを呼び出した次の行でTestCase#fail()を呼び出す。
  • 例外発生をテストしていると読みやすくするため、catch句では例外の変数をexpectedとし、assertTrue(true)とする。

要は、例外が正しく発生しなければcatch句に行かず、fail()が呼ばれてテストが失敗する。
変数expectedやassertTrue()は読みやすさのためだが、このfailを書かずにテストしている人が多かったように感じる(そもそも例外発生のテストを書く人自体が少なかった)。
fail()がないと例外が発生しなくてもテストが成功してしまう。


こうしたややトリッキーなコードであるため、広まっていなかったと思う。
JUnit4では、@Testに期待する例外クラスを記述することができる。
同じテストをJUnit4で書くとこうなる。

import org.junit.Test;

public class JUnit4Sample {

	@Test(expected = IllegalStateException.class)
	public void testExpectedException() {

		Sample s = new Sample();
		s.throwException();
	}

	private static class Sample {
		public void throwException() {
			throw new IllegalStateException();
		}
	}

}

@Testの属性としてexpectedがあり、そこに発生する例外クラスを記述する。
もし発生しなければテストは失敗する。


もちろんTestNGでも同様(というよりこっちが先駆者?)
TestNGではこうなる。

import org.testng.annotations.Test;

public class TestNGSample {
	@Test(expectedExceptions = IllegalStateException.class)
	public void testExpectedException() {

		Sample s = new Sample();
		s.throwException();
	}

	private static class Sample {
		public void throwException() {
			throw new IllegalStateException();
		}
	}
}

アノテーションの属性名が異なるだけだ。expectedExceptionsで指定する。


ただ、違いはある。
TestNGとJUnit4のもっとも大きな違い - Fight the Future じゅくのblogでも述べたように。JUnitはテストの独立性を考えているため、例外クラスは1つしか記述できない。
テストは独立しているのだから、2つの例外が同時に発生することはないし、当然だ。


一方、TestNGはexpectedException「s」となっているように、例外クラスを複数指定できる。
どれが1つの例外が発生しさえすればテストは成功になる。
テストメソッドにパラメータを渡せるため、パラメータによって発生する例外が異なることもあるからだ。