概要
TheoryとDataPointsとFixitureによるパターン化テスト
内容
JUnitのテストは基本的に
・事前条件の設定
・テスト対象処理の実行
・テスト結果の確認(assertThat)
など似たようなコードをコピペする機会が多くなります。
privateメソッドなどにより共通化を行うことは可能ですが、
JUnit4ではテストフレームワークの仕組みとしてこのようなケースを
解決する仕組みが用意してあります。
文法
まずTheoryを使うことを明示するためにクラスのアノテーションに
@RunWith(Theories.class)
を付加します。
次に、テストで扱う可変データを表すFixitureをinnerClassとして自作で定義します。
この際にテストエラー時の補助情報を充実させるためにtoStringメソッドにテスト情報を
含めます。(※我流です)
次にFixtureで定義した型でstaticの配列を定義して@DataPointsアノテーションを付加します。
この配列がそのままテストパターンになります。
この際、staticメソッドでも対応可能であるためメソッド内でデータの取得元を
外部ファイル化することでテストデータをテストクラスから分離することが可能です。
後は、csv・XML・JSON・YAMLなどお好みのフォーマットでデータを定義してみください。
(※後述の例では外部ファイル化までは扱わない)
そして、テスト対象のメソッドに@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: "田中 一郎君" : :