2013年6月10日月曜日

パラメータテスト TheoriesとFixture(前編)

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はテスト自動化シリーズと題しましてJUnit関連の紹介を連載中です。

さて、JUnitの開発では、「渡す引数が違うだけで同じメソッドを何度もテストする」というパラメータテストを実施することがあります。
例えば、「文字列の半角英数字チェック」のメソッドをテストしようとすれば、引数として渡すテスト対象文字列は以下のように色々なパターンが存在します。

  • 半角英字
  • 半角数字
  • 記号
  • 全角英字
  • 全角数字
  • ひらがな
  • 空白スペース
  • 以下続く……

これらのパターンに対してそれぞれテストを実行し、全て正常に期待した結果になるかどうかをチェックするJUnitソースを作成するというシチュエーションです。

では、まずはダメなパターンから行ってみましょう。

@Test
public void 半角英数字チェックのテスト() {
    assertThat(TheoriesSample.isHankaku("a"),is(true));
    assertThat(TheoriesSample.isHankaku("1"),is(true));
    assertThat(TheoriesSample.isHankaku(","),is(false));
    assertThat(TheoriesSample.isHankaku("A"),is(false));
    assertThat(TheoriesSample.isHankaku("あ"),is(false));
    assertThat(TheoriesSample.isHankaku("*"),is(false));
    assertThat(TheoriesSample.isHankaku(""),is(true));
    assertThat(TheoriesSample.isHankaku(null),is(true));
    assertThat(TheoriesSample.isHankaku(" "),is(true));
}

こういう書き方はJUnitの作法としてNGです。
この場合、一つのテストメソッドで複数パターンのテストを実施していることになってしまいます。

「一つのメソッドで一つのテストパターン」

これがJUnitの作法です。
みんなが正しく作法を守ることで可読性の高い良いソースが生まれるのです。
面倒でもテストパターン毎に別々のメソッドにして下さい。

その作法を守ったパターンが以下になります。

@RunWith(Enclosed.class)
public class TheoriesSampleTest {

    public static class 半角英数字チェックのテスト {

        @Test
        public void 半角英数字チェック_半角英字はtrue() {
            assertThat(TheoriesSample.isHankaku("a"),is(true));
        }

        @Test
        public void 半角英数字チェック_半角数字はtrue() {
            assertThat(TheoriesSample.isHankaku("1"),is(true));
        }

        @Test
        public void 半角英数字チェック_記号はfalse() {
            assertThat(TheoriesSample.isHankaku(","),is(true));
        }

        //以下続く
    }

}
これなら「一つのメソッドで一つのテストパターン」のルールを守れます。

前回の記事で紹介した「Enclosedアサーション」を使えば「半角英数字チェックのテスト」でグループ化出来るので、メソッドが増えてもソースの見通しは比較的良い状態を保てます。

とはいえ、いくら正しい書き方と言っても、これは面倒でしょう。
「引数が1コ違うだけで他は全部同じなんだから、引数以外は共通化したい」と思うのがプログラマー精神です。

そこでご紹介するのが、今回記事のテーマ「Theoriesアサーション」です。
この機能は、上記のように「テストを実行するメソッド部分は同じだが、渡すパラメータだけは変えたい」という要望に対応するものです。

まずは以下にサンプルを記載します。

@RunWith(Enclosed.class)
public class TheoriesSampleTest {

    @RunWith(Theories.class)
    public static class 半角英数字チェックのテスト_true系 {

        @DataPoint
        public static String 半角英字 = "a";
        @DataPoint
        public static String 半角数字 = "1";
        @DataPoint
        public static String 空白 = "";
        //省略

        @Theory
        public void 半角英数字チェックパラメータテスト(String str) {
            assertThat(TheoriesSample.isHankaku(str),is(true));
        }

    }

    @RunWith(Theories.class)
    public static class 半角英数字チェックのテスト_false系 {

        @DataPoint
        public static String 記号 = ",";
        @DataPoint
        public static String 全角英字 = "A";
        @DataPoint
        public static String ひらがな = "あ";
        //省略

        @Theory
        public void 半角英数字チェックパラメータテスト(String str) {
            assertThat(TheoriesSample.isHankaku(str),is(false));
        }

    }
}

ここで新登場のアサーションは三つです。

  • @RunWith(Theories.class):このテストクラスがTheoryによる繰り返しであることを示す
  • @DataPoint:繰り返し時に渡すパラメータ
  • @Theory:テストメソッド本体

今まで引数として渡していたパラメータは「@DataPointアサーション」をつけたフィールド変数として定義します。
そして、そのクラスに「@RunWith(Theories.class)」を、テストメソッドに「@Theory」を付与することで、定義した全ての@DataPointがテストメソッドに繰り返し渡されるという構造です。

つまり、「@DataPoint」の定義だけをペタペタと増やしていけば、大量にあるテストパターンのパラメータも全て網羅出来るわけです。

この結果がエラーになった場合は、以下のように表示されます。


なるほど。
普通のテストケースだと「assertThat」の箇所が表示されますが、こちらの場合はエラーになったパラメータが表示されるわけです。

つまり、テストメソッドの中にassertThatが複数あった場合、その中のどのassertThatがエラーになったかは一目では分からないということになります。
このため、テストメソッド本体の中に記述するassertThatは一行一発で済ませるのが良いわけです。

このような場合には、過去に紹介した「カスタムMatcher」を作ることで解決して下さい。今まで紹介したJUnitの機能を組み合わせることで、スタイリッシュなJUnitソースを開発出来るのです。

次回予告

今回の記事ではTheoriesアサーションの簡単な例を紹介しました。
しかし、

  • 「@DataPointでテストメソッドに渡している引数がString型1コしか無いけど、同時に複数個渡したい場合はどうすればいいの?」
  • 「期待する結果がtrue,falseの2パターンだけならこれでいいけど、引数毎に違う場合はどうするんだ?」

という疑問が沸いた方もいらっしゃるかと思います。

そう、上記の例では、テストメソッドに渡す引数は1コ限定、「assertThat」の期待値部分もtrue/falseの固定値と、柔軟性が無いのです。
プロジェクトの都合に合わせて柔軟に対応するにはもう一息、JUnitの機能と言うより、書き方のテクニックが必要になります。
次回はその書き方のテクニック「Fixture」についてご説明します。

2 件のコメント:

  1. アサーションではなく、アノテーションですね。

    返信削除
  2. 確かに。
    失礼しました。。。

    返信削除