【SpringBoot】【Junit5】繰り返しTestの実装方法2パターン(DataPattern,ParameterizedTest)

何にも依存しない一番シンプルな繰り返しテストと、
Junit5のParameterizedTestの実装方法について説明します。

テスト対象の説明

三角形の面積を求めるというシンプルなロジックと、
入力値を特定の値しか認めないロジック。

DemoService

package com.example.demo.service;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
class DemoService {

    int calculateTriangleArea(int base, int height) {
        return (base * height) / 2;
    }

    boolean validation(int height){
        return height == 3 || height == 5;
    }
}

最初のテストはこんな感じです。

    @Test
    void normalTest() {
        //GIVEN
        int base = 6;
        int height = 3;
        int excepted = 9;

        //WHEN
        int actual = target.calculateTriangleArea(base, height);

        //THEN
        assertThat(actual).isEqualTo(excepted);
    }

DataPattern実装

    @Test
    void dataPatternTest() {
        //GIVEN
        for (DataPattern param : List.of(
                new DataPattern(6, 3, 9),
                new DataPattern(9, 4, 18),
                new DataPattern(7, 4, 14)
        )) {
            //WHEN
            int actual = target.calculateTriangleArea(param.base, param.height);

            //THEN
            assertThat(actual).isEqualTo(param.excepted);
        }
    }

    class DataPattern {
        int base;
        int height;
        int excepted;

        DataPattern(int base, int height, int excepted) {
            this.base = base;
            this.height = height;
            this.excepted = excepted;
        }
    }

パラメータ格納用のインナークラスを作り、テストメソッド上でまとめて生成したものを一気にテストしています。

実行結果

テストが失敗した時に、具体的にどこでFailとなったかはわからない。

ParameterizedTest実装

Junit5で可能なテストのやり方です。
ParameterizedTestの実装方法として、パラメータの渡し方で複数のやり方があります。

Junit5の準備

jarを追加します。

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-commons</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
  • junit-jupiter-params
    ParameterizedTestで使用するアノテーションを有効にします
  • junit-platform-commons
    ParameterizedTestでの値の渡し方の一つである、
    CsvSourceで必要となります。
  • junit-jupiter
    Junit5を使えるようにします。

@ArgumentsSource

DataPattern実装のようにパラメータ用のクラスファイルを作成し、
@ArgumentsSourceの引数でクラスごと渡します。
パラメータ用のクラスにはArgumentProviderをimplementsします。

    @ParameterizedTest
    @ArgumentsSource(ArgumentsParameterTest.class)
    void ArgumentsParameterTest(ArgumentsParameterTest args) {

        //WHEN
        int actual = target.calculateTriangleArea(args.base, args.height);

        //THEN
        assertThat(actual).isEqualTo(args.expected);
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    private static class ArgumentsParameterTest implements ArgumentsProvider {
        int base;
        int height;
        int expected;

        @Override
        public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
            return Stream.of(
                    Arguments.of(new ArgumentsParameterTest(6, 2, 6)),
                    Arguments.of(new ArgumentsParameterTest(9, 4, 18)),
                    Arguments.of(new ArgumentsParameterTest(7, 4, 14))
            );
        }
    }

パラメータ用のクラスはstaticクラスにする必要があります。
オブジェクトのパラメータを渡したい時や、ちょっと複雑なAssertionをかけたい時に使えます。
もちろんパラメータとして渡したクラスのメソッドも使えます。

実行結果

パラメータごとでどのようなテストか分かる。
テストFail時もFailとなったパラメータだけ赤くなるので分かりやすい。

ビルダーパターンを使ってパラメータを生成すると以下の感じです。

    @ParameterizedTest
    @ArgumentsSource(ArgumentsParameterTestBuilder.class)
    void ArgumentsParameterTestBuilder(ArgumentsParameterTestBuilder args) {

        //WHEN
        int actual = target.calculateTriangleArea(args.base, args.height);

        //THEN
        assertThat(actual).isEqualTo(args.expected);
    }

    @Builder
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    private static class ArgumentsParameterTestBuilder implements ArgumentsProvider {
        int base;
        int height;
        int expected;

        @Override
        public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
            return Stream.of(
                    Arguments.of(ArgumentsParameterTestBuilder.builder().base(6).height(2).expected(6).build()),
                    Arguments.of(ArgumentsParameterTestBuilder.builder().base(9).height(4).expected(18).build()),
                    Arguments.of(ArgumentsParameterTestBuilder.builder().base(7).height(4).expected(14).build())
            );
        }
    }

@MethodSource

パラメータ用のメソッドを作成して、テストメソッドの引数に渡します。

    @ParameterizedTest
    @MethodSource("methodParameter")
    void methodParameterTest(int base, int height, int expected) {

        //WHEN
        int actual = target.calculateTriangleArea(base, height);

        //THEN
        assertThat(actual).isEqualTo(expected);
    }

    static Stream<Arguments> methodParameter() {
        return Stream.of(
                Arguments.of(6, 2, 6),
                Arguments.of(9, 4, 18),
                Arguments.of(7, 4, 14)
        );
    }

オブジェクトを渡すこともできます。
また@ArgumentsSourceではNestされたクラスの中にはパラメータクラスを作ることはできませんが、
@MethodSourceはTestInstanceを使うことでNestクラス内にも実装できます。

    @Nested
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    class nested {

        @ParameterizedTest
        @MethodSource("methodParameterNest")
        void methodParameterTestNested(int base, int height, int expected) {

            //WHEN
            int actual = target.calculateTriangleArea(base, height);

            //THEN
            assertThat(actual).isEqualTo(expected);
        }

        Stream<Arguments> methodParameterNest() {
            return Stream.of(
                    Arguments.of(6, 2, 6),
                    Arguments.of(9, 4, 18),
                    Arguments.of(7, 4, 14)
            );
        }
    }

実行結果

引数だけがシンプルに表示される。

@CsvSource

複数のパラメータをアノテーションの中でCsv形式で渡すことができます。
オブジェクトは渡すことができません。
パラメータの型は暗黙的に変換されます。
日付だったら「yyyy-MM-dd」とか。

    @ParameterizedTest
    @CsvSource({
            "6,2,6",
            "9,4,18",
            "7,4,14"
    })
    void CsvParameterTest(int base, int height, int expected) {

        //WHEN
        int actual = target.calculateTriangleArea(base, height);

        //THEN
        assertThat(actual).isEqualTo(expected);
    }

Nestクラスの中でも使えます。

実行結果

@MethodSource と同じ表示形式。

@ValueSource

一つのパラメータを渡すことができます。
オブジェクトは渡すことができません。

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 4, 6})
    void ValueParameterTestValue(int height) {

        //WHEN
        boolean actual = target.validation(height);

        //THEN
        assertThat(actual).isFalse();
    }

ParameterizedTestの中では一番シンプルです。
バリデーションチェックのテストとかする時に使います。
Nestクラスの中でも使えます。

このテストだけDemoServiceのvalidationメソッドに対してテストをしています。

実行結果

まとめ

大概のテストは全部@MethodSourceでやってしまえると思っています。
もっと簡単にできそうだと思ったら@CsvSourceか@ValueSourceで。
@ArgumentsSourceはテストの実装自体もちょっとパワー使いますね。

Junitカテゴリの最新記事