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(){
...
}
}
JUnit 5のパラメーター化されたテスト機能は、JUnit 4が提供するものとまったく同じ機能を提供しません。
柔軟性の高い新機能が導入されましたが、パラメータ化されたテストクラスがクラス化されたフィクスチャ/アサーションをクラスレベルのすべてのテストメソッドに対して使用するJUnit4機能も失われました。
「入力」を指定して、テストメソッドごとに_@ParameterizedTest
_を定義することが必要です。
その欠如を超えて、2つのバージョンの主な違いと、JUnit 5でパラメーター化されたテストを使用する方法を紹介します。
TL; DR
あなたの質問であなたのようにテストするためにケースごとに値を指定するパラメータ化されたテストを書くために、 _org.junit.jupiter.params.provider.MethodSource
_ は仕事をするべきです。
_
@MethodSource
_を使用すると、テストクラスの1つ以上のメソッドを参照できます。各メソッドは、Stream
、Iterable
、Iterator
、または引数の配列を返す必要があります。さらに、各メソッドは引数を受け入れてはなりません。デフォルトでは、テストクラスに@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);
}
}
_