Как реализовать параметризованные тесты JUnit 4 в JUnit 5?
В JUnit 4 было легко протестировать инварианты для нескольких классов с помощью @Parameterized
аннотаций. Ключевым моментом является то, что набор тестов выполняется с одним списком аргументов.
Как повторить это в JUnit 5, не используя JUnit-vintage?
@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(){
...
}
}
2 ответа
Функция параметризованного тестирования в JUnit 5 не обеспечивает те же функции, что и в JUnit 4.
Были введены новые функции с большей гибкостью... но также утрачена функция JUnit 4, в которой параметризованный тестовый класс использует параметризованные фикстуры / утверждения на уровне класса, то есть для всех методов тестирования класса.
определяющий @ParameterizedTest
для каждого метода тестирования, указав "вход" так необходимо.
Помимо этого недостатка, я расскажу об основных различиях между двумя версиями и о том, как использовать параметризованные тесты в JUnit 5.
TL; DR
Чтобы написать параметризованный тест, в котором будет указано значение для каждого конкретного теста, org.junit.jupiter.params.provider.MethodSource
должен сделать работу.
@MethodSource
позволяет ссылаться на один или несколько методов тестового класса. Каждый метод должен возвращать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
,
В 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
аннотация вместо. Кроме того, вы должны объявить как минимум один источник, который будет предоставлять аргументы для каждого вызова.
Требование зависимости
Параметризованные тесты не включены в 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 предоставляет несколько вариантов и артефактов для написания параметризованных тестов.
Способы оплаты зависят, как правило, от источника данных, которые вы хотите использовать.
Вот типы источников, предложенные платформой и описанные в документации:
@ValueSource
@EnumSource
@MethodSource
@CsvSource
@CsvFileSource
@ArgumentsSource
Вот 3 основных источника, которые я на самом деле использую с JUnit 5, и я представлю:
@MethodSource
@ValueSource
@CsvSource
Я считаю их базовыми, так как пишу параметризованные тесты. Они должны позволять писать в JUnit 5, тип тестов JUnit 4, который вы описали. @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());
}
}
Если у вас есть несколько параметров метода в методе теста, и вы хотите использовать любой тип в качестве источника, @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
Если у вас есть один параметр метода в методе test, и вы можете представлять источник параметра из одного из этих встроенных типов (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) Отображение между источником и параметром метода может быть сделано между двумя различными типами.
Тип String
использование в качестве источника данных позволяет, в частности, благодаря его синтаксическому анализу преобразовывать в несколько других типов.
3) @CsvSource
Если у вас есть несколько параметров метода в методе теста, @CsvSource
может подойти.
Чтобы использовать это, аннотируйте тест с @CsvSource
и указать в массиве String
каждый случай.
Значения каждого случая разделяются запятой.
подобно @ValueSource
отображение между источником и параметром метода может быть сделано между двумя различными типами.
Вот пример, который иллюстрирует это:
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
В.С. @MethodSource
Эти типы источников служат очень классическому требованию: сопоставление источника с несколькими параметрами метода в тестовом методе.
Но у них другой подход.
@CsvSource
имеет ряд преимуществ: оно четче и короче.
Действительно, параметры определены чуть выше тестируемого метода, нет необходимости создавать метод фиксации, который может дополнительно генерировать "неиспользуемые" предупреждения.
Но это также имеет важное ограничение относительно типов отображения.
Вы должны предоставить массив String
, Каркас предоставляет функции преобразования, но он ограничен.
Подводя итог, в то время как String
предоставляется как источник, и параметры метода теста имеют одинаковый тип (String
-> String
) или полагаться на встроенное преобразование (String
-> int
например), @CsvSource
появляется как способ использования.
Поскольку это не так, вы должны сделать выбор между сохранением гибкости @CsvSource
путем создания собственного конвертера (ArgumentConverter
подкласс) для преобразований, не выполняемых платформой или использующих @MethodSource
с фабричным методом, который возвращает Stream<Arguments>
,
У него есть недостатки, описанные выше, но он также имеет большое преимущество для сопоставления готового любого типа от источника к параметрам.
Преобразование аргумента
О сопоставлении между источниками (@CsvSource
или же @ValueSource
например) и параметры метода испытаний, как видно, каркас позволяет делать некоторые преобразования, если типы не совпадают.
Вот презентация двух типов конверсий:
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);
}
Если для двух типов среда не обеспечивает преобразование, как в случае пользовательских типов, следует использовать ArgumentConverter
,
Явное преобразование
Вместо использования неявного преобразования аргументов вы можете явно указать
ArgumentConverter
использовать для определенного параметра, используя@ConvertWith
аннотация, как в следующем примере.
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);
}
}
Мы всегда должны запускать тестовый пример с разными значениями для пограничного тестирования, один из способов - создать несколько тестовых примеров метода для другого набора входных значений, но он будет иметь много шаблонного кода и не является лучшей практикой или использованием Аннотация JUnit 5 @ParameterizedTest. Метод @Test будет вызываться несколько раз с разными значениями параметров каждый раз.
@ParameterizedTest предназначен только для обозначения того, что параметры этого тестового примера будут переданы во время выполнения, но чтобы объявить, откуда получать входные данные от @ParameterizedTest, требуется любой из следующих источников входных данных.
- @ValueSource
- @ Аргументы
- @ArgumentsProvider
- @ArgumentsSource
- @CsvFileSource
- @CsvSource
- @EnumSource
- @MethodSource
все примеры приведены здесь