読者です 読者をやめる 読者になる 読者になる

日々常々

ふつうのプログラマがあたりまえにしたいこと。

JUnit の @Rule で ExternalResource を使ってみる

Java JUnit

@Rule
うっかりTwitterでつぶやくと、無関係な方にメッセージを送ってしまって迷惑なJavaアノテーションですが、JUnit4.7で追加されたらしい @Rule は結構便利な輩です。
乱暴に言うと、テストクラスに @Rule つけた MethodRuleフィールドを書いておけば、テストメソッドをこね回せます。似たようなものに @Before, @After がありますが、これらとの違いはテストメソッドとの距離感です。単純な前後処理ではなくテストメソッド自体を好き放題…とは言わないまでも、ごにょごにょ出来ます。幾つかのクラスが用意されているので、使い方はそれらを見れば大体解るかと*1。自分で MethodRule 実装してもいいんですけど、大抵は用意されているクラスを拡張すれば事足ります。自分で実装する駄目な例は前に書いたおまじないとかです。

ExternalResource

用意されているクラスの一つに ExternalResource があります。
解りやすい名前ですね。そのまま、外部リソースを何とかするときに使ったらよさそうです。定義されているメソッドも before, after となっていて、メソッド呼び出しの前と finally で呼ばれているので @Before, @After の代わりに使えなくは無いのですが、そこは ExternalResource って名前に敬意を払って堪えて下さい。サンプルだとServerインスタンスの接続/切断メソッドを呼んでますが、Webサーバアプリケーションをメインでやってる私としては、やはりdatabaseの接続/切断を伴うテストに使いたくなります。「UnitTestでdatabase接続するのはどうなんだ?」と言う議論は有りますが、「SQL文が構文的に通るか」程度の検証でも、するとしないとでは結構な差がでますし、不要になれば @Ignore でもしとけば良いだけの話です。と言うことで ExternalResource を使ってコネクションとってこさせて…

public class TestExternalResource {

	private Connection conn = null;

	@Rule
	public MethodRule resource = new ExternalResource() {

		@Override
		protected void before() throws Throwable {
			// Connectionを取得してconnに設定
		}

		@Override
		protected void after() {
			// Connectionを開放する
		}
	};

	@Test
	/* 以下テストが続く */
}

こんな感じですか。でもテストの先頭にこんな無名クラスがあるのは見通しが悪くなるし、それこそ @Before, @After と何が違うのか解りません。ですので ExternalRsource を継承したクラスにします。そしてそのクラスのインスタンスを @Rule をつけたフィールドにいれます。

public class TestResource extends ExternalResource {

	@Override
	protected void before() throws Throwable {
		// Connectionを取得する
	}

	@Override
	protected void after() {
		// Connectionを開放する
	}
};


public class TestExternalResource {

	private Connection conn = null;

	@Rule
	public MethodRule resource = new TestResource();

	@Test
	/* 以下テストが続く */
}

これでテスト側の見通しはぐっと良くな……いやいや、取得したConnectionを設定して無いよ。どうすんだよ。悩みました。すっごく悩みました。時間にして4秒くらい悩みました。で、こんな感じになってしまいました。

public class TestResource extends ExternalResource {

	private Connection conn;
	public Connection get() { return conn; }

	@Override
	protected void before() throws Throwable {
		// Connectionを取得する
	}

	@Override
	protected void after() {
		// Connectionを開放する
	}
};


public class TestExternalResource {

	@Rule
	public TestResource resource = new TestResource();

	@Test
	/* 以下テストが続く */
}

後はテストメソッドで今まで connフィールド を使用していたとこを resource.get() に変更する感じです。とりあえずこれでテストコード側の見通しは良くなった気がします。他にも同様のテストが増えるほど効果は大きくなると思います。継承したり、該当するテスト全てにいちいち @Before, @After をコピペしたりする必要がなくなります。 @Rule いいよ @Rule。…たぶんいいよ。いいよね?



余談ですが、上のような感じでやったらMaven testがエラー出すようになりました。TestResourceをテストしたいのにテストメソッドが一つもないって。テストじゃねーよ、と思ったけど、Mavenはテスト対象を引っ張ってくるのにクラス名であててるらしく。

By default, the Surefire Plugin will automatically include all test classes with the following wildcard patterns:
"**/Test*.java" - includes all of its subdirectories and all java filenames that start with "Test".
"**/*Test.java" - includes all of its subdirectories and all java filenames that end with "Test".
"**/*TestCase.java" - includes all of its subdirectories and all java filenames that end with "TestCase".

http://maven.apache.org/plugins/maven-surefire-plugin/examples/inclusion-exclusion.html

こんな感じで書いてるので、 TestResource なんて名前はアウトです。TestConnectionも当然駄目です。だから私は名前を変えました。名前変えたくなかったら、 pom.xml の除外に書くか @Ignore をつけるかになるのでしょうか。