web-dev-qa-db-ja.com

JUnit 5でJUnit 4パラメータ化されたテストを実装する方法

JUnit 4では、 @Parameterized アノテーションを使用することで、一連のクラスにわたって不変条件を簡単にテストできました。重要なことは、テストのコレクションが単一の引数リストに対して実行されていることです。

JUnit-vintageを使用せずにJUnit 5でこれを複製する方法は?

@ParameterizedTest はテストクラスには適用されません。 @TestTemplate 適切なように聞こえますが、そのアノテーションのターゲットもメソッドです。


このようなJUnit 4テストの例は次のとおりです。

@RunWith( Parameterized.class )
public class FooInvariantsTest{

   @Parameterized.Parameters
   public static Collection<Object[]> data(){
       return new Arrays.asList(
               new Object[]{ new CsvFoo() ),
               new Object[]{ new SqlFoo() ),
               new Object[]{ new XmlFoo() ),
           );
   }

   private Foo fooUnderTest;


   public FooInvariantsTest( Foo fooToTest ){
        fooUnderTest = fooToTest;
   }

   @Test
   public void testInvariant1(){
       ...
   }

   @Test
   public void testInvariant2(){
       ...
   } 
}
22
Sled

JUnit 5のパラメーター化されたテスト機能は、JUnit 4が提供するものとまったく同じ機能を提供しません。
柔軟性の高い新機能が導入されましたが、パラメータ化されたテストクラスがクラス化されたフィクスチャ/アサーションをクラスレベルのすべてのテストメソッドに対して使用するJUnit4機能も失われました。
「入力」を指定して、テストメソッドごとに_@ParameterizedTest_を定義することが必要です。
その欠如を超えて、2つのバージョンの主な違いと、JUnit 5でパラメーター化されたテストを使用する方法を紹介します。

TL; DR

あなたの質問であなたのようにテストするためにケースごとに値を指定するパラメータ化されたテストを書くために、 _org.junit.jupiter.params.provider.MethodSource_ は仕事をするべきです。

_@MethodSource_を使用すると、テストクラスの1つ以上のメソッドを参照できます。各メソッドは、StreamIterableIterator、または引数の配列を返す必要があります。さらに、各メソッドは引数を受け入れてはなりません。デフォルトでは、テストクラスに@TestInstance(Lifecycle.PER_CLASS)の注釈が付けられていない限り、このようなメソッドは静的でなければなりません。

単一のパラメーターのみが必要な場合は、次の例に示すように、パラメーター型のインスタンスを直接返すことができます。

JUnit 4と同様に、_@MethodSource_はファクトリメソッドに依存し、複数の引数を指定するテストメソッドにも使用できます。

JUnit 5では、JUnit 4に最も近いパラメーター化されたテストを記述する方法です。

JUnit 4:

_@Parameters
public static Collection<Object[]> data() {
_

JUnit 5:

_private static Stream<Arguments> data() {
_

主な改善点:

  • _Collection<Object[]>_は_Stream<Arguments>_になり、柔軟性が向上します。

  • ファクトリメソッドをテストメソッドにバインドする方法は少し異なります。
    現在は短くなり、エラーが発生しにくくなりました。コンストラクタを作成し、各パラメータの値を設定するフィールドを宣言する必要がなくなりました。ソースのバインドは、テストメソッドのパラメーターで直接行われます。

  • JUnit 4では、同じクラス内で、_@Parameters_を使用して宣言する必要があるファクトリメソッドは1つだけです。
    JUnit 5では、この制限が解除されています。実際に、ファクトリメソッドとして複数のメソッドを使用できます。
    そのため、クラス内で、異なるファクトリメソッドを参照する@MethodSource("..")で注釈が付けられたいくつかのテストメソッドを宣言できます。

たとえば、次のサンプルテストクラスは、いくつかの加算計算をアサートします。

_import Java.util.stream.Stream;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;    
import org.junit.jupiter.api.Assertions;

public class ParameterizedMethodSourceWithArgumentsTest {

  @ParameterizedTest
  @MethodSource("addFixture")
  void add(int a, int b, int result) {
     Assertions.assertEquals(result, a + b);
  }

  private static Stream<Arguments> addFixture() {
    return Stream.of(
      Arguments.of(1, 2, 3),
      Arguments.of(4, -4, 0),
      Arguments.of(-3, -3, -6));
  }
}
_

既存のパラメーター化されたテストをJUnit 4からJUnit 5にアップグレードするには、_@MethodSource_を検討する必要があります。


要約

_@MethodSource_にはいくつかの長所がありますが、いくつかの短所もあります。
パラメーター化されたテストのソースを指定する新しい方法がJUnit 5で導入されました。
ここでは、一般的な方法でどのように対処するかについての幅広いアイデアを提供できることを願っています、それらに関するいくつかの追加情報(完全ではありません).

はじめに

JUnit 5は パラメータ化されたテスト機能 をこれらの用語で導入します:

パラメータ化されたテストにより、異なる引数を使用してテストを複数回実行できます。通常の_@Test_メソッドと同じように宣言されますが、代わりに_@ParameterizedTest_アノテーションを使用します。さらに、各呼び出しの引数を提供するソースを少なくとも1つ宣言する必要があります。

依存関係の要件

パラメーター化されたテスト機能は、_junit-jupiter-engine_コア依存関係に含まれていません。
それを使用するには、特定の依存関係を追加する必要があります:_junit-jupiter-params_。

Mavenを使用する場合、これは宣言する依存関係です。

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

データの作成に使用できるソース

JUnit 4とは異なり、JUnit 5はパラメーター化されたテストを作成するための複数のフレーバーとアーティファクトを提供します
一般に、優先する方法は、使用するデータのソースによって異なります。

フレームワークによって提案され、 documentation で説明されているソースタイプを次に示します。

  • _@ValueSource_
  • _@EnumSource_
  • _@MethodSource_
  • _@CsvSource_
  • _@CsvFileSource_
  • _@ArgumentsSource_

JUnit 5で実際に使用する3つの主なソースを以下に示します。

  • _@MethodSource_
  • _@ValueSource_
  • _@CsvSource_

パラメータ化されたテストを書くのと同じくらい基本的なものと考えています。彼らはあなたが説明したJUnit 4テストのタイプであるJUnit 5で書くことを許可するべきです。
_@EnumSource_、_@ArgumentsSource_、_@CsvFileSource_はもちろん役に立ちますが、より専門的です。

_@MethodSource_、_@ValueSource_および_@CsvSource_ のプレゼンテーション

1)_@MethodSource_

このタイプのソースでは、ファクトリメソッドを定義する必要があります。
しかし、それは多くの柔軟性も提供します。

JUnit 5では、JUnit 4に最も近いパラメーター化されたテストを記述する方法です。

テストメソッドに単一メソッドパラメータがあり、任意のタイプをソースとして使用する場合、_@MethodSource_は非常に良い候補者。
それを実現するには、各ケースの値のストリームを返すメソッドを定義し、@MethodSource("methodName")でテストメソッドに注釈を付けます。ここで、methodNameはこのデータソースメソッドの名前です。

たとえば、次のように記述できます。

_import Java.util.stream.Stream;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

public class ParameterizedMethodSourceTest {

    @ParameterizedTest
    @MethodSource("getValue_is_never_null_fixture")
    void getValue_is_never_null(Foo foo) {
       Assertions.assertNotNull(foo.getValue());
    }

    private static Stream<Foo> getValue_is_never_null_fixture() {
       return Stream.of(new CsvFoo(), new SqlFoo(), new XmlFoo());
    }

}
_

テストメソッドに複数のメソッドパラメータがあり、 any type をソースとして使用する場合、_@MethodSource_も非常に良い候補者。
それを実現するには、テストするケースごとに_org.junit.jupiter.params.provider.Arguments_のストリームを返すメソッドを定義します。

たとえば、次のように記述できます。

_import Java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;    
import org.junit.jupiter.api.Assertions;

public class ParameterizedMethodSourceWithArgumentsTest {

    @ParameterizedTest
    @MethodSource("getFormatFixture")
    void getFormat(Foo foo, String extension) {
        Assertions.assertEquals(extension, foo.getExtension());
    }

    private static Stream<Arguments> getFormatFixture() {
    return Stream.of(
        Arguments.of(new SqlFoo(), ".sql"),
        Arguments.of(new CsvFoo(), ".csv"),
        Arguments.of(new XmlFoo(), ".xml"));
    }
}
_

2)_@ValueSource_

テストメソッドに単一メソッドパラメータがあり、これらの組み込みタイプ(String、int、 long、double)、_@ValueSource_が適しています。

_@ValueSource_は実際にこれらの属性を定義します:

_String[] strings() default {};
int[] ints() default {};
long[] longs() default {};
double[] doubles() default {};
_

たとえば、次のように使用できます。

_import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

public class ParameterizedValueSourceTest {

    @ParameterizedTest
    @ValueSource(ints = { 1, 2, 3 })
    void sillyTestWithValueSource(int argument) {
        Assertions.assertNotNull(argument);
    }

}
_

注意1)複数のアノテーション属性を指定してはなりません。
注意2)ソースとメソッドのパラメーター間のマッピングは、2つの異なるタイプ間で行うことができます。
データのソースとして使用されるタイプStringは、特にその解析のおかげで、他の複数のタイプに変換できます。

3)_@CsvSource_

複数のメソッドパラメータがテストメソッドにある場合、_@CsvSource_が適しています。
それを使用するには、テストに_@CsvSource_の注釈を付け、Stringの配列で各ケースを指定します。
各ケースの値はカンマで区切られます。

_@ValueSource_と同様に、ソースとメソッドのパラメーター間のマッピングは、2つの異なるタイプ間で行うことができます。
これはそれを説明する例です:

_import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

public class ParameterizedCsvSourceTest {

    @ParameterizedTest
    @CsvSource({ "12,3,4", "12,2,6" })
    public void divideTest(int n, int d, int q) {
       Assertions.assertEquals(q, n / d);
    }

}
_

_@CsvSource_ VS _@MethodSource_

これらのソースタイプは、非常に古典的な要件を満たしています。ソースからテストメソッドの複数のメソッドパラメータへのマッピングです。
しかし、彼らのアプローチは異なります。

_@CsvSource_にはいくつかの利点があります。明確で短いです。
実際、パラメータはテストされたメソッドのすぐ上で定義されており、さらに「未使用」の警告を生成する可能性があるフィクスチャメソッドを作成する必要はありません。
しかし、マッピングタイプに関する重要な制限もあります。
Stringの配列を提供する必要があります。フレームワークは変換機能を提供しますが、制限があります。

要約すると、ソースとして提供されているStringとテストメソッドのパラメーターは同じ型(String-> String)であるか、組み込みの変換(String-> intなど)、_@CsvSource_が使用方法として表示されます。

そうではないので、フレームワークでは実行されない変換用のカスタムコンバーター(ArgumentConverterサブクラス)を作成して、_@CsvSource_の柔軟性を維持することを選択する必要がありますまたは _@MethodSource_を返すファクトリメソッドで_Stream<Arguments>_を使用します。
これには上記の欠点がありますが、ソースからパラメーターに任意の型をそのままマッピングできることも大きなメリットです。

引数変換

ソース(_@CsvSource_または_@ValueSource_など)とテストメソッドのパラメーターの間のマッピングについては、フレームワークでは、型が同じでない場合にいくつかの変換を行うことができます。

ここ は、2種類の変換のプレゼンテーションです。

3.13.3。引数変換

暗黙の変換

_@CsvSource_のようなユースケースをサポートするために、JUnit Jupiterは多くの組み込みの暗黙的な型コンバーターを提供しています。変換プロセスは、各メソッドパラメータの宣言されたタイプによって異なります。

.....

Stringインスタンスは現在、暗黙的に次のターゲットタイプに変換されています。

_Target Type          |  Example
boolean/Boolean      |  "true" → true
byte/Byte            |  "1" → (byte) 1
char/Character       |  "o" → 'o'
short/Short          |  "1" → (short) 1
int/Integer          |  "1" → 1
.....
_

たとえば、前の例では、ソースからのStringとパラメーターとして定義されたintの間で暗黙的な変換が行われます。

_@CsvSource({ "12,3,4", "12,2,6" })
public void divideTest(int n, int d, int q) {
   Assertions.assertEquals(q, n / d);
}
_

そしてここでは、暗黙的な変換がStringソースからLocalDateパラメータに行われます。

_@ParameterizedTest
@ValueSource(strings = { "2018-01-01", "2018-02-01", "2018-03-01" })
void testWithValueSource(LocalDate date) {
    Assertions.assertTrue(date.getYear() == 2018);
}
_

2つの型の場合、フレームワークによって変換が提供されない場合(カスタム型の場合)、ArgumentConverterを使用する必要があります。

明示的な変換

暗黙の引数変換を使用する代わりに、次の例のように_@ConvertWith_アノテーションを使用して、特定のパラメーターに使用するArgumentConverterを明示的に指定できます。

JUnitは、特定のArgumentConverterを作成する必要があるクライアントにリファレンス実装を提供します。

明示的な引数コンバーターは、テスト作成者によって実装されることを意図しています。したがって、junit-jupiter-paramsは、参照実装としても機能する単一の明示的な引数コンバーターJavaTimeArgumentConverterのみを提供します。合成アノテーションJavaTimeConversionPatternを介して使用されます。

このコンバーターを使用したテスト方法:

_@ParameterizedTest
@ValueSource(strings = { "01.01.2017", "31.12.2017" })
void testWithExplicitJavaTimeConverter(@JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) {
    assertEquals(2017, argument.getYear());
}
_

JavaTimeArgumentConverterコンバータークラス:

_package org.junit.jupiter.params.converter;

import Java.time.LocalDate;
import Java.time.LocalDateTime;
import Java.time.LocalTime;
import Java.time.OffsetDateTime;
import Java.time.OffsetTime;
import Java.time.Year;
import Java.time.YearMonth;
import Java.time.ZonedDateTime;
import Java.time.chrono.ChronoLocalDate;
import Java.time.chrono.ChronoLocalDateTime;
import Java.time.chrono.ChronoZonedDateTime;
import Java.time.format.DateTimeFormatter;
import Java.time.temporal.TemporalQuery;
import Java.util.Collections;
import Java.util.LinkedHashMap;
import Java.util.Map;

import org.junit.jupiter.params.support.AnnotationConsumer;

/**
 * @since 5.0
 */
class JavaTimeArgumentConverter extends SimpleArgumentConverter
        implements AnnotationConsumer<JavaTimeConversionPattern> {

    private static final Map<Class<?>, TemporalQuery<?>> TEMPORAL_QUERIES;
    static {
        Map<Class<?>, TemporalQuery<?>> queries = new LinkedHashMap<>();
        queries.put(ChronoLocalDate.class, ChronoLocalDate::from);
        queries.put(ChronoLocalDateTime.class, ChronoLocalDateTime::from);
        queries.put(ChronoZonedDateTime.class, ChronoZonedDateTime::from);
        queries.put(LocalDate.class, LocalDate::from);
        queries.put(LocalDateTime.class, LocalDateTime::from);
        queries.put(LocalTime.class, LocalTime::from);
        queries.put(OffsetDateTime.class, OffsetDateTime::from);
        queries.put(OffsetTime.class, OffsetTime::from);
        queries.put(Year.class, Year::from);
        queries.put(YearMonth.class, YearMonth::from);
        queries.put(ZonedDateTime.class, ZonedDateTime::from);
        TEMPORAL_QUERIES = Collections.unmodifiableMap(queries);
    }

    private String pattern;

    @Override
    public void accept(JavaTimeConversionPattern annotation) {
        pattern = annotation.value();
    }

    @Override
    public Object convert(Object input, Class<?> targetClass) throws ArgumentConversionException {
        if (!TEMPORAL_QUERIES.containsKey(targetClass)) {
            throw new ArgumentConversionException("Cannot convert to " + targetClass.getName() + ": " + input);
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        TemporalQuery<?> temporalQuery = TEMPORAL_QUERIES.get(targetClass);
        return formatter.parse(input.toString(), temporalQuery);
    }

}
_
14
davidxxx