JUnitのテスト実行時の流れ
JUnitは、以下の順序でテストを実行する。
- テストケースの収集
- テストの実行
- テスト結果(レポート)の出力
このうち、「1.テストケースの収集」を行う際の挙動を定義する
仕組みがテストランナーである。
JUnitのテスト収集時の挙動
実際のコードを見ながら、テスト収集の流れを理解する。
テストクラスの実装
public class FooTest {
@Test
public void インスタンス化するとnullでなくなる() {
Foo sut = new Foo();
assertThat(sut, is(notNullValue()));
}
// エントリーポイント
public static void main(String[] args) {
org.junit.runner.JUnitCore.main(FooTest.class.getName());
}
}
コマンドラインからの実行
コマンドラインにテストクラスを指定し、テストを実行する。
すると、エントリーポイントが実行され、
JUnitCoreクラスが@Test
がついたメソッドをテストケースとして収集する。
$ java -cp [junit-4.10.jarのパス] org.junit.runner.JUnitCore test.FooTest
テストランナーが行うこと
テストランナーとは、アノテーションの付与によって
テストクラスのエントリーポイント内の実装を省略できるというイメージ。
(XXXがついているメソッドは実行しない、XXXクラスのみ実行する など)
テストランナーのチートシート
共通事項
テストランナーは、テストクラス毎にorg.junit.runner.RunWithアノテーションを
付与し、値にテストランナークラスを渡すことで指定する。
何も指定せず、RunWithアノテーションのみを付与した場合は、
デフォルトでorg.junit.runners.JUnit4クラスが設定される。
JUnit4:単一のテストクラス内を全件実行する
全件実行を行う場合などに使用する。以下の要件を満たすメソッドを全て収集する。
- org.junit.Testアノテーションが付与されている
- publicメソッドである
- 引数を取らず、戻り値がvoidである
@RunWith(JUnit4.class)
public class JUnit4Test {
@Test
public void fooTest() {
fail("自動失敗");
}
/*
* エントリーポイントに関する記述がなくなった
*/
}
Suite:複数のテストクラスを指定する
- テストスイートクラスを定義し、テストランナーにorg.junit.runners.Suiteクラスを指定する。
- org.junit.runners.Suite.SuiteClassesアノテーションに対象となるテストクラスを指定する。
- テストスイートクラスを実行すると、指定されたテストクラスが実行される。
※ テストスイートクラスは慣例的に「〜Tests」、「〜AllTests」と命名される。
@RunWith(Suite.class)
@SuiteClasses({ FooTest.class, BarTest.class })
public class FooAllTests {
/*
* テストスイートクラス内には処理は記述しない
*/
}
※個別のテストクラスの記述は省略
Enclosed:構造化したテストクラスを実行する
JUnitでは、テストクラスにインナークラスを定義することが可能である。
よって、「初期処理が同じ」「前提条件が同じ」などの共通項をくくり出してインナークラスを
作成することで、テストケースの可読性が上がる場合がある。
こういったテストケースの実行時に使用するのが、org.junit.runners.Enclosedクラスである。
@RunWith(Enclosed.class)
public class EnclosedTest {
public static class リストが空の場合 {
List<String> sut;
@Before
public void setUp() throws Exception {
sut = new ArrayList<>();
}
@Test
public void sizeが0を返却する() throws Exception {
assertThat(sut.size(), is(0));
}
}
public static class リストが1件の場合 {
List<String> sut;
@Before
public void setUp() throws Exception {
sut = new ArrayList<>();
sut.add("apple");
}
@Test
public void sizeが1を返却する() throws Exception {
assertThat(sut.size(), is(1));
}
}
}
※インナークラスは個別の標準的なテストクラスとして認識されるので、Beforeアノテーションなどを設定可能
Theories:テストデータをパラメータ化する
テストデータをパラメータ化して、テストケースとテストデータを分離する際に使用する。
パラメータを切り替えるだけで、同じテストケースを異なるデータでテストすることが可能。
テストしたい振る舞いは同じで、渡す引数の組み合わせが多い時に有効である。
@RunWith(Theories.class)
public class TheoriesTest {
@DataPoints
private static int[][] VALUES = {
{1, 0, 1},
{0, 2, 1},
{1, 2, 2}
};
@Test
public void calcTest() {
Calculator cal = new Calculator();
// 渡された2つの引数を加算する
int[] actual = cal.plus(VALUES[0], VALUES[1]);
assertThat(actual, is(VALUES[2]));
}
}
Categories:カテゴライズして特定のカテゴリのみを操作する
org.junit.experimental.categories.Categoriesクラスはテストケースのカテゴライズを行う。
特定カテゴリのテストのみを行う(もしくは行わない)といった操作が可能となる。
Categoriesクラスの実装
テストケースのカテゴライズには、以下の3つのクラスの実装が必要。
・カテゴリーの目印となるカテゴリークラス(※実装はインターフェース)
・カテゴリーの指定を行うテストスイートクラス
・テストケースを定義したテストクラス
以下は、テストケースを成功と失敗でカテゴライズし、
失敗するケースのみを実行対象から除外するように実装。
public interface NotExcecuteTests {
/*
* カテゴリークラス内には処理は記述しない
*/
}
@RunWith(Categories.class)
@ExcludeCategory(NotExcecuteTests.class) /* 含めたいときは@IncludeCategory */
@SuiteClasses(TestCases.class)
public class CategoriesTests {
/*
* テストスイートクラス内には処理は記述しない
*/
}
public class TestCases {
@Test
public void successTest() {
String value = "foobar";
assertThat(value, is(value));
}
@Test
@Category(NotExcecuteTests.class)
public void failTest1() {
fail("自動失敗");
}
@Test
@Category(NotExcecuteTests.class)
public void failTest2() {
fail("自動失敗");
}
}
カテゴリ化を行うテストのパターン
カテゴリ化が必要となるパターンはそこまで多くはない。
スローテスト問題が生じるパターンは以下3つに集中するので、
以下のテストケースが発生したらカテゴリを付与しておくのが推奨される。
-
データベーステスト
データベース接続を行うテストでは、データベースの初期化、
コネクションの準備、必要データの作成など多くのリソースを使う。 -
通信処理を伴うテスト
外部のWebサービスへの接続など、ネットワークを利用するテストケース。
ネットワークの状態により実行速度が左右されたり、
サービスが停止している場合にはテストが失敗したりと状態が不安定な上、
実行を繰り返すとサービスに不要な負荷を与えることもある。
よって、毎回の実行を避けるためカテゴリ化をすることが望ましい。 -
GUIテスト
GUIのテストも、アプリケーションの起動やページのレンダリングに時間がかかったり、
テスト実行中に端末の操作ができなくなるなど、
実行に対するコストが高いためカテゴリ化が推奨される。
参考文献
この記事は以下の情報を参考にして執筆しました。