JUnitテストプログラミング¶
JUnitテスト環境¶
NetBeans IDEでは、ユニットテストツールの1つとしてJUnitを選択することができます。
ステップバイステップでテストケースの記述¶
JUnitでテストケースを作成する例を、単純なものから徐々に機能を付け足して見ていきます。
最小限のテストケース¶
まずは、最小限のテストケースを記述したサンプルです。
ソースコードを表示
ポイント- (1) テスト対象クラスFooと同じパッケージでテストケースクラスFooTestを定義します。
- (2) フィールドsut1にテスト対象クラスのインスタンスを保持します。
- (3) テストメソッドは@Testアノテーションを付与し、戻り値void、引数なし、テスト内容を表す日本語のメソッド名2で定義します。
- (4) テスト結果の判定は、assertThat文などのassert系メソッドを使用し、実行結果と期待値を引数に渡します。
1 テスト対象とするシステムをSystem Under Testと呼ぶのでその頭辞語sutをフィールド名とします。
fn2. テストケース一覧表のテストケース名をそのままメソッド名にすると対応が容易です。
テストの初期化、後始末処理を記述¶
複数のテストメソッドを実行するにあたって、共通の初期化処理、後始末処理がある場合、各テストメソッドに同じ処理を書くのはDRY原則を踏みにじるので、@Before、@Afterアノテーションをつけたメソッドに一本化します。
ソースコードを表示
ポイント- (1) テストメソッド実行前に呼ばれるsetUp1メソッドを@Beforeアノテーションを付与して定義します。
- (2) テストメソッド実行後に呼ばれるtearDown2メソッドを@Afterアノテーションを付与して定義します。リソースの解放が必要なテスト対象クラスなどの場合に有用です。
1 JUnit 3時代のメソッド名setUpを習慣で使い続けているもので、別な名前を使ってもかまいません。
2 注釈1と同様です。
一連のテストケース実行前後に一度だけ初期化、後始末処理を記述¶
一連のテストメソッドを実行する前に一度だけ初期化処理を実行し、一連のテストメソッドを実行した後に一度だけ後始末処理を実行したい場合、@BeforeClass、@AfterClassアノテーションをつけたメソッドを記述します。
ソースコードを表示
ポイント- (1) 一連のテスト実行前に一度だけ呼ばれるメソッド(setUpClass)を@BeforeClassアノテーションを付与して定義します。ライブラリの初期化など一度だけ実行する場合に有用です。
- (2) テストメソッド実行後に呼ばれるメソッド(tearDown)を@AfterClassアノテーションを付与して定義します。ライブラリの後始末など一度だけ実行する場合に有用です。
異常系のテストで例外がスローされた場合に成功と判定したい¶
異常値を入力して例外がスローされることをテストで確認したい場合の記述例です。
ソースコード断片を表示
ポイント- @Testアノテーションに、expected属性で例外クラスを指定します。
- スローされる例外の型だけでなく、メッセージまで識別したい場合、ExpectedExceptionを使う
- Ruleを使う?
テストのグルーピング¶
テストランナーEnclosed.classを使って、ネストクラスごとにテストを実行します。
ネストクラスごとに@Beforeなどで初期化を行うことができます。
ソースコードを表示
注意点¶
- 外側のクラスに@RunWith(Enclosed.class)を指定し、かつ内部クラスに@RunWith(Enclosed.class)を指定するとテストメソッドが実行されません。
- 内部クラスと同レベルでテストメソッドを定義してもテストメソッドは実行されません。
同じテストメソッドに複数のデータを入力してテストを実行する¶
いくつか手段があります。
- テストランナーParameterizedを使う
- テストランナーTheoriesを使う
歴史的経緯で最初にParameterizedが導入され、その後Theoriesが導入されました。今ではTheoriesを使うのがいいようです。
テストランナーTheoriesと、@DataPointおよび@DataPointsアノテーションを使用¶
- テストメソッドは引数を取るように定義
- 2つ以上の引数を定義した場合、総当りの組み合わせで引数に値が入る
テストランナーTheoriesと、@TestedOnアノテーションを使用¶
テストメソッドに引数を設け、引数に@TestedOnアノテーションで複数の値を記述します。
その値ごとにテストメソッドを呼び出します。
上述の@DataPointsをテストメソッドにインラインで定義するようなイメージです。
ソースコードを表示
- 2つ以上の引数を定義した場合、総当りの組み合わせで引数に値が入る
- TestedOnアノテーションはVer.4.10現在でint[]型の属性intsのみ定義されている
- 他の型をテストメソッド引数で扱いたい場合は、自前でアノテーションとParameterSupplierのサブタイプとを定義する
テストランナーTheoriesと、入力値・期待値の組み合わせを保持するTextureクラスの定義¶
入力値、期待値を、別々のフィールドで@DataPointsで定義し、テストメソッドの引数2つで入力値と期待値を受け取ると、本来の入力値ー期待値の組み合わせではなく、総当りで引数に値が入れられてしまいます。
そこで、一つの入力値ー期待値の組を保持するFixtureクラスを定義し、@DataPointsではFixtureクラスの配列を定義します。
テストメソッドの引数は、Fixture型を1つ取るようにします。
ソースコードを表示
テストケース記述のあれこれ¶
テストメソッドを記述する単位¶
最小単位でテストメソッドを記述する¶
1つのメソッドでは1つのassert文という意見もありますが、テストの意味が1つであれば複数のassert文があってもよいと考えます。
逆に、意味が複数になるのであれば、メソッドを分けて書くのがいいです。
意味が複数であれば、バグがそれぞれに起因して別々に発生するので、バグの絞込みも容易になり、テスト記述内容も明瞭になります。
2つの意味を1つのメソッドに書いた例を 表示
2つの意味を2つのメソッドにそれぞれ書いた例を 表示
期待値と結果の照合¶
文字列の照合¶
文字列が完全一致¶
@Test
public void 同一名である() {
sut.setName("Thomas");
assertThat(sut.getName(), is("Thomas"));
}
org.hamcrest.CoreMatchers.is
は、等価判定なので文字列の場合 String#equalsTo
と同じ判定となります。
大文字小文字を無視して一致¶
インスタンス(参照先)が一致¶
org.hamcrest.CoreMatchers.sameInstance
を追記します。
@Test
public void 同一の属性インスタンスが取得できる() {
sut.setAttributes(attribute);
assertThat(sut.getAttributes(), is(sameInstance(attribute));
}
真偽の一致¶
@Test
public void 真となる() {
assertThat(sut.isPrimary(), is(true));
}
@Test
public void 偽となる() {
assertThat(sut.isSecondary(), is(not(true)));
}