そもそも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メンバをチェックでき、内部状態の変化もテスト可能になる。
この方法のデメリットとして、以下を指摘する人がいるだろう。
- テスト対象コードの中にテストコードが入るのでごちゃごちゃする。
- リリースするクラスの一部として、inner classが含まれてしまう。
だが、電化製品であっても、メンテナンス用部品は配備したままリリースされているはずだ。本来、外部に公開するべきでない要素をテストするのだから、こういった妥協は必要なものと考える。