Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

JUnit | JUnit4のDataPointsによるテストとFixtureと流れるようなインターフェース

パンくず

Java
JUnit
JUnit4のDataPointsによるテストとFixtureと流れるようなインターフェース

概要

JUnit4のDataPointsによるテストとFixtureと流れるようなインターフェース

内容

JUnit4のDataPointsによって、テストをパラメータ化することができます。
その際、テストケースの入れ物としてFixtureを利用します。

Fixtureは下記ページのような内容に対してフィールドに変数を設定し、
その内容をコンストラクタ経由で配列に設定します。

JUnit4のDataPointsによるテストとパラメータ化の基準

この際、項目が増えるとコンストラクタの項目数が増加して一見して
どのようなテストデータが設定されているか分かりにくくなります。
そこで、流れるようなインターフェースを利用して設定内容を分かりやすくします。

サンプルコード

テストコード仕様やテストコード以外のソースコードは下記リンクのサンプルと同じものとする。
JUnit4のDataPointsによるテストとパラメータ化の基準

package parameterized;

import static jp.co.dgic.testing.framework.DJUnitTestCase.setReturnValueAtAllTimes;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import gr.java_conf.tb.tbpg_util.common.CommonUtil;

import org.junit.experimental.runners.Enclosed;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;

@RunWith(Enclosed.class)
public class SampleParameterized2Test {

  @RunWith(Theories.class)
  public static class GetUpperUserName {

    @DataPoints
    public static Fixture[] getFixture() {
      /*
       * ケース番号、ユーザー名、囲い文字有無、例外有無、例外メッセージ、ユーザー名期待値
       */
      Fixture[] fixture = {
          // 大文字変換なし、囲い文字有りのケース
          new Fixture().caseNo(1).uerName("andy").hasEnclose(true).hasException(false).errorMessage(null)
              .expectedUserName("【andy】"),
          // 大文字変換なし、囲い文字なしのケース
          new Fixture().caseNo(2).uerName("andy").hasEnclose(false).hasException(false).errorMessage(null)
              .expectedUserName("andy"),
          // 大文字変換あり、囲い文字有りのケース
          new Fixture().caseNo(3).uerName("boby").hasEnclose(true).hasException(false).errorMessage(null)
              .expectedUserName("【BOBY】"),
          // 大文字変換あり、囲い文字なしのケース
          new Fixture().caseNo(4).uerName("boby").hasEnclose(false).hasException(false).errorMessage(null)
              .expectedUserName("BOBY"),
          // 例外になるケース
          new Fixture().caseNo(5).uerName("boby12").hasEnclose(false).hasException(true).errorMessage("取り敢えず例外")
              .expectedUserName("boby12"),
          // わざとエラーになるケース
          new Fixture().caseNo(6).uerName("boby").hasEnclose(false).hasException(false).errorMessage(null)
              .expectedUserName("boby"),
      };
      return fixture;
    };

    @Theory
    public void testGetUpperUserName(Fixture fixture) {
      // ****テスト前処理*****
      // DBから取得するユーザー名をモック化
      setReturnValueAtAllTimes(DbAccess.class, "getUserName", fixture.userName);
      SampleParameterized parameterized = new SampleParameterized();
      String actualUserName;
      // ****テスト実行****
      try {
        actualUserName = parameterized.getUpperUserName(fixture.hasEnclose);
        if (fixture.hasException) {
          // 例外のケースにも関わらず正常終了していたら強制的にテスト失敗
          assertThat(fixture.toString(), true, is(false));
        }
        // ****テスト結果の検証****
        assertThat(fixture.toString(), actualUserName, is(fixture.expectedUserName));
      } catch (Exception e) {
        if (!fixture.hasException) {
          // 正常系のケースにも関わらず例外が発生していたら強制的にテスト失敗
          e.printStackTrace();
          assertThat(fixture.toString(), true, is(false));
        }
        // 例外のメッセージ検証
        assertThat(fixture.toString(), e.getMessage(), is(fixture.errorMessage));
      }

    }

    static class Fixture {
      // Fixtureのケース番号
      int caseNo;
      // モックに設定するユーザー名。また分岐に影響する
      String userName;
      // 分岐に影響する入力値。
      boolean hasEnclose;
      // 例外の有無
      boolean hasException;
      // 例外のメッセージ内容
      String errorMessage;
      // ユーザー名の期待値。分岐に影響する
      String expectedUserName;

      public String toString() {
        // Fixtureの内容を一括出力
        return CommonUtil.getAllFiledInfo(Fixture.class, this);
      }

      public Fixture caseNo(int caseNo) {
        this.caseNo = caseNo;
        return this;
      }

      public Fixture uerName(String userName) {
        this.userName = userName;
        return this;
      }

      public Fixture hasEnclose(boolean hasEnclose) {
        this.hasEnclose = hasEnclose;
        return this;
      }

      public Fixture hasException(boolean hasException) {
        this.hasException = hasException;
        return this;
      }

      public Fixture errorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
        return this;
      }

      public Fixture expectedUserName(String expectedUserName) {
        this.expectedUserName = expectedUserName;
        return this;
      }
    }
  }

}

Fixture部を抜粋して比較

通常のFixture

       /*
       * ケース番号、ユーザー名、囲い文字有無、例外有無、例外メッセージ、ユーザー名期待値
       */
      Fixture[] fixture = {
          // 大文字変換なし、囲い文字有りのケース
          new Fixture(1, "andy", true, false, null, "【andy】"),
          // 大文字変換なし、囲い文字なしのケース
          new Fixture(2, "andy", false, false, null, "andy"),
          // 大文字変換あり、囲い文字有りのケース
          new Fixture(3, "boby", true, false, null, "【BOBY】"),
          // 大文字変換あり、囲い文字なしのケース
          new Fixture(4, "boby", false, false, null, "BOBY"),
          // 例外になるケース
          new Fixture(5, "boby12", false, true, "取り敢えず例外", "boby12"),
          // わざとエラーになるケース
          new Fixture(6, "boby", false, false, null, "boby"),
      };

流れるようなインターフェースのFixture

      /*
       * ケース番号、ユーザー名、囲い文字有無、例外有無、例外メッセージ、ユーザー名期待値
       */
      Fixture[] fixture = {
          // 大文字変換なし、囲い文字有りのケース
          new Fixture().caseNo(1).uerName("andy").hasEnclose(true).hasException(false).errorMessage(null)
              .expectedUserName("【andy】"),
          // 大文字変換なし、囲い文字なしのケース
          new Fixture().caseNo(2).uerName("andy").hasEnclose(false).hasException(false).errorMessage(null)
              .expectedUserName("andy"),
          // 大文字変換あり、囲い文字有りのケース
          new Fixture().caseNo(3).uerName("boby").hasEnclose(true).hasException(false).errorMessage(null)
              .expectedUserName("【BOBY】"),
          // 大文字変換あり、囲い文字なしのケース
          new Fixture().caseNo(4).uerName("boby").hasEnclose(false).hasException(false).errorMessage(null)
              .expectedUserName("BOBY"),
          // 例外になるケース
          new Fixture().caseNo(5).uerName("boby12").hasEnclose(false).hasException(true).errorMessage("取り敢えず例外")
              .expectedUserName("boby12"),
          // わざとエラーになるケース
          new Fixture().caseNo(6).uerName("boby").hasEnclose(false).hasException(false).errorMessage(null)
              .expectedUserName("boby"),
      };

前者は項目順がどの項目をセットしているか暗記するか、
都度Fixtureのコンストラクタを確認する必要がありましたが、
後者は設定内容が一目瞭然です。