Tbpgr Blog

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

JUnit | TheoryとDataPointsとFixitureによるパターン化テスト

パンくず

Java
JUnit
TheoryとDataPointsとFixitureによるパターン化テスト

概要

TheoryとDataPointsとFixitureによるパターン化テスト

内容

JUnitのテストは基本的に
・事前条件の設定
・テスト対象処理の実行
・テスト結果の確認(assertThat)
など似たようなコードをコピペする機会が多くなります。

privateメソッドなどにより共通化を行うことは可能ですが、
JUnit4ではテストフレームワークの仕組みとしてこのようなケースを
解決する仕組みが用意してあります。

文法

まずTheoryを使うことを明示するためにクラスのアノテーション

@RunWith(Theories.class)

を付加します。

次に、テストで扱う可変データを表すFixitureをinnerClassとして自作で定義します。
この際にテストエラー時の補助情報を充実させるためにtoStringメソッドにテスト情報を
含めます。(※我流です)

次にFixtureで定義した型でstaticの配列を定義して@DataPointsアノテーションを付加します。
この配列がそのままテストパターンになります。
この際、staticメソッドでも対応可能であるためメソッド内でデータの取得元を
外部ファイル化することでテストデータをテストクラスから分離することが可能です。
後は、csvXMLJSONYAMLなどお好みのフォーマットでデータを定義してみください。
(※後述の例では外部ファイル化までは扱わない)

そして、テスト対象のメソッドに@Theoryアノテーションを付加して
自作したFixitureを引数に指定して、内部でFixitureのメンバを参照すれば
パラメーター化テストの完成です。

サンプルコード

実コード

public class SampleTheories {
  public String getName(String fullName, NameTypeEnum nameType) {
    fullName += appendSama();
    String[] names = fullName.split(" ");

    switch (nameType) {
    case FIRST_NAME:
      return names[1];
    case FAMILY_NAME:
      return names[0];
    case FULL_NAME:
      return fullName;
    default:
      return "";
    }
  }

  private String appendSama() {
    return "様";
  }
}

使用するEnum

package gr.java_conf.tb.java_sample_code.junit;

public enum NameTypeEnum {
	FIRST_NAME("1"),FAMILY_NAME("2"),FULL_NAME("3");

	private String type;
	private NameTypeEnum(final String type) {
		this.type = type;
	}
	public String toValue() {
		return this.type;
	}
	 public NameTypeEnum fromValue(String value) throws Exception {
	    if (value.equals("1")) {
	      return FIRST_NAME;
	    } else if (value.equals("2")) {
	      return FAMILY_NAME;
	    } else if (value.equals("3")) {
	      return FULL_NAME;
	    } else {
	      throw new IllegalArgumentException();
	    }
	  }
}

テストコード

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import static jp.co.dgic.testing.framework.DJUnitTestCase.*;

@RunWith(Theories.class)
public class SampleTheoriesTest {
  @DataPoints
  public static Fixture[] DATAS = { new Fixture(1, "田中 一郎", NameTypeEnum.FAMILY_NAME, "田中"),
      new Fixture(2, "田中 一郎", NameTypeEnum.FIRST_NAME, "一郎君"),
      new Fixture(3, "田中 一郎", NameTypeEnum.FULL_NAME, "田中 一郎君"),
      new Fixture(4, "田中 一郎", NameTypeEnum.FULL_NAME, "田中 一aaa郎"),// エラーになるケースをわざと混ぜた
      };

  @Before
  public void setup() {
    // ※DataPointsの配列数分setupが呼ばれる
    System.out.println("setup");
  }

  // DataPointを利用したセオリーテスト
  @Theory
  public void testGetNameTheory(Fixture fixture) {
    SampleTheories sampleTheories = new SampleTheories();
    // djUnitのバーチャルモックオブジェクトも併用可能
    setReturnValueAtAllTimes(SampleTheories.class, "appendSama", "君");
    String actual = sampleTheories.getName(fixture.fullName, fixture.nameType);
    assertThat(fixture.toString(), actual, is(fixture.expected));
  }

  // 通常のテストケースとの共存も可能
  @Test
  public void testGetNameNormal() {
    SampleTheories sampleTheories = new SampleTheories();
    String actual = sampleTheories.getName("佐藤 秀夫", NameTypeEnum.FAMILY_NAME);
    assertThat(actual, is("佐藤"));
  }

  // 通常のテストケースとの共存も可能
  @SuppressWarnings("unused")
  @Test(expected = NullPointerException.class)
  public void testGetNameError() {
    SampleTheories sampleTheories = new SampleTheories();
    String actual = sampleTheories.getName("佐藤 秀夫", null);
  }

  /**
   * テスト用Fixture。
   *
   */
  static class Fixture {
    int caseNo;
    /** フルネーム */
    String fullName;
    /** 名前種別 */
    NameTypeEnum nameType;
    /** 期待値 */
    String expected;

    Fixture(int caseNo, String fullNamem, NameTypeEnum nameType, String expected) {
      this.caseNo = caseNo;
      this.fullName = fullNamem;
      this.nameType = nameType;
      this.expected = expected;
    }

    public String toString() {
      return "\nNo" + this.caseNo + "\nフルネーム=" + this.fullName + "\n" + "名前種別=" + this.nameType + "\n" + "期待値="
          + this.expected + "\n";
    }
  }

}

JUnitの結果に表示されるトレース内容

org.junit.experimental.theories.internal.ParameterizedAssertionError: testGetNameTheory(DATAS[3])
	at org.junit.experimental.theories.Theories$TheoryAnchor.reportParameterizedError(Theories.java:183)
:
:
Caused by: java.lang.AssertionError: 
No4
フルネーム=田中 一郎
名前種別=FULL_NAME
期待値=田中 一aaa郎

Expected: is "田中 一aaa郎"
     got: "田中 一郎君"

:
: