どうしてもprivateメンバをテストしたい人に対する、JUnit4での解決案

そもそもJUnitは、振る舞いのテスト(≒ブラックボックステスト)を前提としている(こんなデータを入れたら、こんな結果が返りますよーという意味で)。

privateメンバをテストしたいというのは、ホワイトボックステストをしたいということである(このメソッドは内部で別のメンバを使ってますよーという意味で)。

実際のところ、JUnitでは、privateメンバのテスト手段は提供されていない。

何とかしてprivateメンバをテストしたい、ということで、リフレクションを利用する人がいる。だが、リフレクションを利用するとリファクタリングが難しくなる(フィールド名を文字列で指定することになるため)。テストコードの書き方のせいで、リファクタリング作業が阻害されるのでは、本末転倒である。

そこで、どうしてもprivateメンバをテストしないと気が済まない人のため、別の解決案を考えた。

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;

// 以下の指定により、inner classをテストクラスとして実行可能になる。
@RunWith(Enclosed.class)
public class Order {
	private List<Item> items = new ArrayList<Item>();
	
	public void add(Item newItem) {
		items.add(newItem);
	}

	/**
	 * privateメンバをテストするテストクラス
	 *
	 */
	public static class OrderPrivateMemberTest {
		@Test
		public void testAdd_success() {
			Item newItem = new Item();
			newItem.setName("apple");
			
			Order testTarget = new Order();
			assertThat(testTarget.items.size(),is(0));
			testTarget.add(newItem);
			assertThat(testTarget.items.size(),is(1));
			assertThat(testTarget.items.get(0).getName(),is("apple"));
		}
	}
}
public class Item {
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

テスト対象コード内にテストクラスを実装する。つまり、テストクラスをinner classとして実装する。JUnit4では、@RunWith(Enclosed.class)指定により、inner classをテスト対象にできる。
これにより、きちんとコンパイルにてprivateメンバをチェックでき、内部状態の変化もテスト可能になる。

この方法のデメリットとして、以下を指摘する人がいるだろう。

  1. テスト対象コードの中にテストコードが入るのでごちゃごちゃする。
  2. リリースするクラスの一部として、inner classが含まれてしまう。

だが、電化製品であっても、メンテナンス用部品は配備したままリリースされているはずだ。本来、外部に公開するべきでない要素をテストするのだから、こういった妥協は必要なものと考える。