LoginSignup
23

More than 5 years have passed since last update.

JUnitParams を使ったパラメータライズドなテスト

Last updated at Posted at 2015-12-12

JUnit を使ったパラメータライズドなテストについて、新たな選択肢として「JUnitParams」というライブラリを使用してみます。

JUnitParams
https://github.com/Pragmatists/JUnitParams

基本的な使い方

JUnitParams の Example にも書かれているが、この手の基本的な使い方は、既出のパラメータライズドなテストでも似たような書き方で、かつそれほど使う機会も少ないと思うので軽く飛ばしつつ。

まずランナーに JUnitParamsRunner を指定。
使用するアノテーションは @junitparams.Parameters
これを @Test とセットで使用する。

ちなみに JUnitParams では、複数のパラメータで検証を行う中で、失敗するケースがあったとしても、全てのパラメータでテストを行う。
⇒ つまり、途中で失敗しても、ケース全てのテストを実施する

@RunWith(JUnitParamsRunner.class)
public class JUnitParamsSample {

    // 値の区切りには「|」(パイプ)の他に「,」も使える
    @Test
    @Parameters({ "A|1", "BBB|3", "CCCCC|5" })
    public void paramsInAnnotationPipeSeparated(String actual, Integer expected) {
        assertThat(actual).hasSize(expected);
    }
}

パラメータ用のメソッド指定

テストメソッドと、そのメソッドに渡すパラメータを定義するメソッドを、いくつかのルールで関連付けることが可能。

デフォルトの命名ルール

デフォルトの命名ルールは簡単。対象となるテストメソッド名の頭に「parametersFor」を付けるだけ。

// パラメータとして parametersForParamsInDefaultMethod の戻り値を使って呼ばれる
@Test
@Parameters
public void paramsInDefaultMethod(String actual, Integer expected) {
    assertThat(actual).hasSize(expected);
}

private Object parametersForParamsInDefaultMethod() {
    return new Object[] {
            new Object[] { "A", 1 },
            new Object[] { "BBB", 3 },
            new Object[] { "CCCCC", 5 }
    };
}

個別指定

また、@Parametersmethod 属性を使って個別にパラメータ用のメソッドを指定することも可能。

@Test
@Parameters(method = "param1, param2, param3")
public void paramsInNamedMethod(String actual, Integer expected) {
    assertThat(actual).hasSize(expected);
}

private Object param1() {
    return new Object[] { "A", 1 };
}
private Object param2() {
    return new Object[] { "BBB", 3 };
}
private Object param3() {
    return new Object[] { "CCCCC", 5 };
}

実践的な使い方

テストコード内で Fixture を定義するケース

いわゆる Fixture を使ったパラメータ指定の場合も、基本的な書き方となんら変わりない。

@Test
@Parameters
public void paramsInCollectionFixture(Fixture f) {
    assertThat(f.actual).hasSize(f.expected);
}

// パラメータを返すメソッドの戻り値は Object の他に Collection, Iterator も可
private List<Fixture> parametersForParamsInCollectionFixture() {
    Fixture f1 = new Fixture("A", 1);
    Fixture f2 = new Fixture("BBB", 3);
    Fixture f3 = new Fixture("CCCCC", 5);
    return Arrays.asList(f1, f2, f3);
}

public static final class Fixture {
    public String actual;
    public Integer expected;

    public Fixture(String actual, Integer expected) {
        this.actual = actual;
        this.expected = expected;
    }
}

リソースファイル内で定義した Fixture 情報を Mapper 経由で変換するケース

Fixture の元ネタとなる情報を、リソースファイル (csv, etc) に定義するような場合、@junitparams.FileParameters を使ってファイルの内容を Fixture にマッピングするための Mapper を使って指定する。

// TestMethod
@Test
@FileParameters(value = "src/test/resources/params/fixture.csv", mapper = FixtureMappter.class)
// ↓コッチでもOK
// @FileParameters(value = "classpath:params/fixture.csv", mapper = FixtureMapper.class)
public void loadParamsFromAnyFile(Fixture f) {
    assertThat(f.actual).hasSize(f.expected);
}

// Mapper
public class FixtureMapper extends CsvWithHeaderMapper {
    @Override
    public Object[] map(Reader reader) {
        Object[] map = super.map(reader);
        List<Object[]> result = new LinkedList<Object[]>();
        for (Object lineObj : map) {
            String line = (String) lineObj;
            String[] values = line.split(",");
            result.add(new Object[] { values[0], Integer.parseInt(values[1]) });
        }
        return result.toArray();
    }
}

リソースファイルはこんな感じ。

文字列, 期待値
A,1
BBB,3
CCCCC,5
DDDD,4

リソースファイル内で定義した Fixture 情報を Converter 経由で変換するケース

上記の例の気持ち悪いところが、Mapper の戻り値が Object[] になるように実装しなければいけないところ。
※汎用的な作りにするためには致し方ないのかな...

それに対して Converter では、TypeSafe なコーディングが可能。
ただし、試した感じだと、リソースファイルに定義された元ネタの情報が「,」「|」で区切られていると、正しく変換できないので、TSV ファイルや独自のセパレーターを使用する必要がある。

// TestMethod
@Test
@FileParameters("classpath:params/no_header_fixture.tsv")
public void convertParams(@FixtureParam Fixture f) {
    assertThat(f.actual).hasSize(f.expected);
}

// 独自の ParamAnnotation
import junitparams.converters.Param;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@Param(converter = FixtureConverter.class) // Converter を指定
public @interface FixtureParam {
    // 独自の属性を定義可能
}

// 独自の Converter
import junitparams.converters.Converter;

public static class FixtureConverter implements Converter<FixtureParam, Fixture> {

    @Override
    public void initialize(FixtureParam annotation) {
        // NOP
    }

    @Override
    public Fixture convert(Object param) throws ConversionFailedException {

        String line = param.toString();
        String[] values = line.split("\t");
        if (values.length != 2) {
            throw new ConversionFailedException("failed");
        }

        return new Fixture(values[0], Integer.parseInt(values[1]));
    }
}

リソースファイル (タブ区切り) はこんな感じ。

A   1
BBB 3
CCCCC   5
DDDD    4

その他

パラメータ化したテストケース毎に名前を指定

@junitparams.naming.TestCaseName アノテーションでケース名を指定可能。

@Test
@Parameters(method = "param1, param2, param3")
@TestCaseName("length({0}) = {1}")
public void paramsInNamedMethod(String p1, Integer p2) {
    assertThat(p1.length(), is(p2));
}

上記のテストケースにおいて、@TestCaseName アノテーションを指定しない場合と、指定した場合とで Eclipse の実行結果を比較。
JUnit の実行結果レベルでも見やすいのは親切ですね。パッと見てどのテストケースか分かるのはイイネ!!

@TestCaseName 指定なし

JUnitParams_1.png

@TestCaseName 指定あり

JUnitParams_2.png

最後に

JUnit に用意されている TheoriesParameterized にやきもきしている方は、お試しいかが?

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23