java что такое параметризированный тест

Это продолжение туториала по JUnit 5. Введение опубликовано здесь.

Оглавление

Источники тестовых аргументов

Параметризованные тесты с несколькими аргументами.

1. Настройка

Последнюю версию можно найти по этой ссылке.

pom.xml

2. Аннотация @ParameterizedTest

Используйте аннотацию @ParameterizedTest, чтобы выполнить тест несколько раз, но с разными аргументами. Нам не нужно использовать аннотацию @Test, вместо этого в таких тестах используется только аннотация @ParameterizedTest.

Мы должны объявить по крайней мере один источник аргументов, предоставляющий аргументы для каждого вызова, которые будут использоваться в тестовом методе.

Используйте аргумент name в аннотации @ParameterizedTest, чтобы настроить отображаемое сообщение.

3. Источники тестовых аргументов

Есть несколько способов передать аргументы методу тестирования. Давайте изучим их.

3.1. Аннотация @ValueSource

Используйте @ValueSource для простых буквальных значений, таких как примитивы и строки.

Она определяет один массив значений и может использоваться только для предоставления одного аргумента для каждого параметризованного вызова теста.

Java поддерживает автобоксирование, поэтому мы также можем использовать литералы в их классах-оболочках.

Мы не можем передавать null в качестве аргумента даже для типов String и Class.

3.2. Аннотация @NullSource

Она предоставляет единственный null аргумент методу, аннотированному @ParameterizedTest.

3.3. Аннотация @EmptySource

Она предоставляет метод, аннотированный @ParameterizedTest, с единственным пустым аргументом следующих типов:

примитивные массивы (например, int [])

массивы объектов (например, String [])

3.4. Аннотация @NullAndEmptySource

Проверка null и non-null значений в одном тесте

3.5. Аннотация @EnumSource

Это удобный способ использования Enum констант. Метод тестирования будет вызываться для каждой константы перечисления за раз.

В данном примере тестовый метод будет вызываться 4 раза, по одному разу для каждой Enum константы.

3.6. Аннотация @MethodSource

Она используется для ссылки на один или несколько фабричных методов тестового класса или внешних классов. Фабричный метод должен генерировать поток аргументов, где каждый аргумент в потоке будет использоваться методом, аннотированным @ParameterizedTest.

Кроме того, фабричный метод не должен принимать аргументы.

Также поддерживаются потоки для примитивных типов (DoubleStream, IntStream и LongStream).

3.7. Аннотация @CsvSource

Эта аннотация позволяет нам задавать списки аргументов как значения, разделенные запятыми. Каждый CSV токен представляет собой строку CSV и приводит к одному вызову параметризованного теста.

Задайте для свойства ignoreLeadingAndTrailingWhitespace значение true или false, указывающее на то, что Junit должен принимать или игнорировать пробелы в CSV токенах.

3.8. Аннотация @CsvFileSource

Эта аннотация очень похожа на @CsvSource за исключением того, что мы читаем токены CSV из файла вместо чтения токенов в исходном тексте. CSV файл можно прочитать по classpath или из локальной файловой системы.

3.9. Аннотация @ArgumentsSource

Аннотацию @ArgumentsSource можно использовать для указания настраиваемого многоразового поставщика аргументов ArgumentsProvider.

4. Параметризованные тесты с несколькими аргументами.

Чтобы написать тесты, которые могут использовать несколько аргументов, мы можем использовать следующие аннотации:

4.1. Аннотация @CsvSource

Как показано в предыдущем разделе 3.7, с помощью аннотации @CsvSource мы можем предоставить множество литералов и простых типов аргументов.

Нам нужно предоставить все аргументы в одном токене CSV, а затем определить соответствующие аргументы метода.

4.2. Интерфейс ArgumentsProvider

5. Вывод

Junit 5 аннотация @ParameterizedTest очень полезна при написании тестов, которые необходимо вызывать для тестирования несколько раз, но с разными аргументами. Junit предоставляет несколько способов, которые можно использовать декларативно для предоставления аргументов методу тестирования.

Мы также можем комбинировать разные аннотации для тестирования различных типов входных данных в одном тесте.

Есть некоторые более сложные понятия, такие как агрегатор аргументов, конвертер аргументов и т.д. Пожалуйста, прочитайте официальную документацию Junit для получения последней информации.

Источник

Учимся параметризировать тесты в JUnit

Начиная с 4-й версии, в JUnit введена параметризация тестов. Она позволяет значительно уменьшить избыточность кода. Разберемся, как работать с параметризацией.

По определению, параметризация это функция “позволяющая запускать тестовый метод каждый раз с разными параметрами”.

В конце узнаем о некоторых полезностях, которые можно применять в своей практике, на практическом примере.

1. Начинаем

Для начала нужно разбираться в Spring Boot и JUnit.

Итак, с чем будем работать:

1.1. Добавляем зависимость JUnit

Настраиваем проект (возможно стоит ознакомиться с указаниями, как это делать), далее добавляем в pom.xml такие строчки:

Первая из зависимостей включает не только JUnit, но и еще пару библиотек, которые которые могут понадобиться для тестирования нашего приложения: mockito и hamcrest. Hamcrest будет задействован для получения провайдеров аргументов (далее объясню, что это).

2. Настраиваем сценарий

2.1. Работаем с одним аргументом

Представим, что надо работать с функцией, которая проверяет введенный возраст, больше ли он, чем 21 год.

Теперь создаем тест, в котором проверяем, выдаст ли ошибку в ответ на другие аргументы.

Сейчас нам нужно ввести аннотации @ParameterizedTest и @ValueSource, чтобы JUnit “понимал”, что будут передаваться разные аргументы. Такие аннотации позволяют применять аргументы самых разных типов, а именно: short, byte, int, long, float, double, char, boolean, string, и даже class.

Мы передали 4 аргумента, и это значит, что тест запустится 4 раза. В первой итерации переменная age примет значение 10, далее 0, 15, 20.

После завершения тестов увидим в окне Test Results результат каждой итерации.

2.2. Передача нескольких аргументов

Теперь создадим простую функцию, умножающую два целых числа.

Представим, что нужно протестировать эту функцию, передавая в нее два аргумента со значением 2. Их умножение должно быть равно 4 (если правила арифметики не поменялись).

Пока видим, что в тесте без проблем. Но есть вещи, которые нужно иметь в виду.

Хороший тестировщик знает, что такой простой тест не охватывает все возможные варианты, нам надо протестировать параметры так, чтобы код приложения работал при любых условиях. Например, в данном случае результат не должен быть равным нулю или отрицательным.

Поэтому и нужны параметризованные тесты с несколькими аргументами. Смотрим на код ниже.

Здесь видим, что есть какое-то @CsvSource вслед за @ParameterizedTest.

Первая итерация получит аргументы 2, 3, 6, которые “привязаны” к сигнатуре метода, простым их объявлением.

Важная вещь которую нужно иметь в виду: аргументы “привязываются” точно в том порядке, в котором передавались. А именно:

Здесь может возникнуть вопрос, почему передается String, если метод calculatedTotalPrice предполагает только int-аргументы? А вот здесь видна полезность JUnit.

Иногда это называют “преобразованием аргументов”, или приведением их типов (Argument Conversion). Эта тема выходит за рамки обзорной статьи, но ее стоит упомянуть.

Кратко посмотрим, что делает такое преобразование:

Должно быть, впечатляет. Это автоматическая конвертация строкового формата в LocalDate. Желательно сверяться с документацией, работая с преобразованием типов.

Читайте также:  какой нагрузкой испытывается спасательная веревка

2.3. Работаем с CSV-файлом

В предыдущем примере мы задействовали @CsvSource, несмотря на то что передали как аргумент строковое значение (String). Но бывает проблема, когда данных очень много, или если нужно тестировать много сценариев. Это превращается в хаос, тест нечитаемый.

Но есть способ сделать CSV-файл “правильным”.

Сперва создадим файл testData.csv в папке src/test/resources, и вставим в него такие строчки:

Мы добавили аннотацию @CsvFileSource, имеющую аргумент “resources”, который должен быть добавлен в адрес CSV-файла. (Если этот файл расположен в папке по умолчанию — src/test/resources — то можно указать только его название с расширением, файл будет найден автоматически).

2.4. Источник Enum-данных

Аннотация @EnumSource говорит о передаче данных из Enum. Обычно это бывает при тестировании бизнес-логики.

2.5. Аннотация MethodSource

Иногда может понадобиться передать несколько параметров (их набор) в несколько тестов. Для этого придумана аннотация @MethodSource.

Она загружает внешнюю функцию, выступающей в роли “поставщика аргументов”. Так можно передать набор параметров в несколько тестов.

Функция DataProvider может добавляться к любому пакету, нужно ввести полный путь к нему.

Здесь все, что нужно сделать, это декларировать пакет с этой функцией, вставить символ хэштега #, и затем название функции, и это будет аргумент аннотации.

3. Бонус.

Можно кастомизировать отображаемое имя тестов.

Как мы помним, в нашем тесте было:

При его запуске увидим следующее:

Вывод можно визуально приукрасить, следующими тегами:

Все что нужно сделать, это передать аргумент name внутри @parametrizedTest.

Надеемся, материал был не только сложным, но и полезным.

В.Нобрэ — тестировщик в компании PicPay (финтех)

Источник

Unit-тестирование, детальное рассмотрение параметризованных тестов. Часть I

Доброго времени суток, коллеги.

Я решил поделиться своим видением на параметризованные юнит-тесты, как делаем это мы, и как возможно не делаете (но захотите делать) вы.

Хочется написать красивую фразу, о том что тестировать надо правильно, и тесты это важно, но до меня сказано и написано уже много материала, я лишь попробую резюмировать и осветить то, что по моему мнению людьми редко используется(понимается), на что в основном задвигается.

Основная цель статьи показать, как можно(и нужно) перестать захламлять свой юнит-тест кодом для создания объектов, и как декларативно создать тестовые данные, если одних mock(any()) не хватает, а таких ситуаций полно.

Создадим maven проект, добавим в него junit5, junit-jupiter-params и mokito

Чтоб не было совсем скучно начнем писать сразу с теста, как любят апологеты TDD, нам нужен сервис, который мы будем декларативно тестировать, подойдет любой, пускай это будет HabrService.

Создадим тест HabrServiceTest. В поле класса теста добавим ссылку на HabrService:

создадим сервис через ide (легким нажатием шортката), добавим на поле аннотацию @InjectMocks.

Приступаем непосредственно к тесту: HabrService в нашем небольшом приложении будет иметь один единственный метод handle(), который будет принимать один единственный аргумент HabrItem, и теперь наш тест выглядит так:

Добавим в HabrService метод handle(), который будет возвращать id нового поста на хабре после его модерации и сохранения в БД, и принимает тип HabrItem, так же создадим наш HabrItem, и теперь тест компилируется, но падает.

Дело в том что мы добавили проверку, на ожидаемое возвращаемое значение.

Также, я хочу убедиться, что в ходе вызова метода handle(), были вызваны ReviewService и PersistanceService, вызвались они строго друг за другом, отработали ровно по 1 разу, и никакие другие методы уже не вызывались. Иными словами вот так:

Добавим в поля класса класса reviewService и persistenceService, создадим их, добавим им методы makeRewiew() и makePersist() соответственно. Теперь все компилируется, но конечно же тест красный.

В контексте данной статьи, реализации ReviewService и PersistanceService не так уж важны, важна реализация HabrService, сделаем его чуть интересней чем он есть сейчас:

и с помощью конструкций when().then() замокируем поведение вспомогательных компонентов, в итоге наш тест стал вот таким и теперь он зеленый:

Макет для демонстрации мощи параметризованных тестов готов.

Добавим в нашу модель запроса к сервису HabrItem поле с типом хаба, hubType, создадим enum HubType и включим в него несколько типов:

а модели HabrItem добавим геттер и сеттер, на созданное поле HubType.

Предположим, что в недрах нашего HabrService спрятался switch, который в зависимости от типа хаба делает с запросом неведомое что-то, и в тесте мы хотим протестировать каждый из кейсов неведомого, наивная реализация метода выглядела бы так:

Можно сделать ее чуть красивей и удобней, сделав тест параметризированным и добавить в него в качестве параметр случайное значение из нашего enum, в итоге декларация теста станет выглядеть так:

красиво, декларативно, и все значения нашего enum обязательно будут задействованы при каком-нибудь очередном запуске тестов, у аннотации есть параметры, можем добавлять стратегии на include, exclude.

Но возможно я вас не убедил, что параметризованные тесты — это хорошо. Добавим в
исходный запрос HabrItem новое поле editCount, в которое будет записано количество тысяч раз, которое пользователи Хабра редактируют свою статью, перед тем как запостить, чтоб она вам хоть немного понравилась, и положим что где то в недрах HabrService есть какая то логика, которая делает неведомое что-то, в зависимости от того, насколько попыток автор постарался, что если я не хочу писать 5 или 55 тестов на все возможные варианты editCount, а хочу протестировать декларативно, и где то в одном месте сразу обозначить все значения которые я хотел бы проверить. Нет ничего проще, и воспользовавшись api параметризованных тестов получим в декларации метода что то такое:

Налицо проблема, мы хотим собирать в параметрах тестового метода сразу два значения декларативно, можно использовать еще один прекрасный метод параметризованных тестов @CsvSource, отлично подойдет для того чтоб протестировать несложные параметры, с несложным выходным значением(крайне удобен в тестировании утилитных классов), но что если объект станет гораздо сложней? Скажем, в нем будет порядка 10 полей, причем не только примитивы и джава-типы.

На помощь приходит аннотация @MethodSource, наш тестового метода стал ощутимо короче и в нем нет больше сеттеров, а источник входящего запроса подается в тестовый метод параметром:

Читайте также:  что делать для роста мышечной массы

в аннотации @MethodSource указана строка generateSource, что это? это название метода, который соберет для нас нужную модель, его декларация будет выглядеть так:

для удобства формирование стрима аргументов nextStream я вынесес в отдельный утилитный тестовый класс:

Теперь в при запуске теста, в параметр тестового метода декларативно будет добавляться модель запроса HabrItem, причем запускаться тест будет столько раз, сколько аргументов сгененрирует наша тестовая утилита, в нашем случае от 1 до 10.

Это может быть особо удобно, если модель в стриме аргументов собирается не хардкодом, как в нашем примере, а с помощью рандомайзеров.(да здравствует плавающие тесты, но ведь если они есть — есть и проблема).

По моему уже все супер, в тесте теперь описано только поведение наших заглушек, и ожидаемые результаты.

Но вот незадача, в модель HabrItem добавляется новое поле, text, массив строк, которое может быть очень большим или не очень, неважно, главное то, что мы не хотим захламять наши тесты, нам не нужны рандомные данные, мы хотим строго определенную модель, с конкретными данными, собирать ее в тесте или где либо еще — мы не хотим. Было бы круто, если бы можно было взять тело json запроса откуда угодно, например из постмана, сделать на его основе моковый файл и в тесте формировать модель декларативно, указав лишь путь к json файлу с данными.

Отлично. Используем аннотацию @JsonSource, которая будет принимать параметр path, с относительным путем к файлу и целевой класс. Черт! В параметризованных тестах нет такой аннотации, а хотелось бы.

Давайте напишем сами.

Обработкой всех аннотаций идущих в комплекте с @ParametrizedTest в junit занимаются ArgumentsProvider, мы напишем свой собственный JsonArgumentProvider:

MockDataProvider, это класс, для парсинга моковых json файлов, его реализация крайне простая:

Моковый провайдер готов, провайдер аргументов для нашей аннотации тоже, осталось добавить саму аннотацию:

Ура. Наша аннотация готова к употреблению, тестовый метод стал таким:

в моковом json мы можем наплодить сколь угодно и очень быстро пачку нужным нам объектов, и нигде отныне нет отвлекающего от сути теста кода, по формированию тестовых данных, конечно часто можно обойтись моками, но далеко не всегда.

Резюмируя, я хотел бы сказать следующее: часто мы работаем как привыкли работать, годами, не задумываясь о том, что некоторые вещи можно сделать красиво и просто, зачастую, используя стандартный api библиотек, которые мы юзаем годами, но не знаем всех их возможностей.

Источник

Руководство по параметризованным тестам JUnit 5

1. Обзор

JUnit 5, следующее поколение JUnit, облегчает написание тестов разработчика с новыми и блестящими функциями.

Одной из таких функций является р arameterized тесты. Эта функция позволяет нам многократно выполнять один метод тестирования с разными параметрами.

В этом руководстве мы собираемся подробно изучить параметризованные тесты, так что приступим!

2. Зависимости

Чтобы использовать параметризованные тесты JUnit 5, нам необходимо импортировать артефакт junit-jupiter-params с платформы JUnit. Это означает, что при использовании Maven мы добавим в наш pom.xml следующее :

Кроме того, при использовании Gradle мы укажем его немного иначе:

3. Первое впечатление

Допустим, у нас есть существующая функция полезности, и мы хотели бы быть уверены в ее поведении:

Параметризованные тесты похожи на другие тесты, за исключением того, что мы добавляем аннотацию @ParameterizedTest :

Средство выполнения тестов JUnit 5 выполняет этот тест, а следовательно, и метод isOdd шесть раз. И каждый раз он присваивает другое значение из массива @ValueSource параметру числового метода.

Итак, этот пример показывает нам две вещи, которые нам нужны для параметризованного теста:

Есть еще одна вещь, которая не очевидна в этом примере, так что следите за обновлениями.

4. Источники аргументов

Как мы уже должны знать, параметризованный тест выполняет один и тот же тест несколько раз с разными аргументами.

4.1. Простые ценности

Например, предположим, что мы собираемся протестировать наш простой метод isBlank :

Мы ожидаем, что этот метод вернет истину для null для пустых строк. Итак, мы можем написать параметризованный тест вроде следующего, чтобы подтвердить это поведение:

Как мы видим, JUnit запускает этот тест два раза и каждый раз присваивает один аргумент из массива параметру метода.

Одним из ограничений источников значений является то, что они поддерживают только следующие типы:

4.2. Нулевые и пустые значения

Начиная с JUnit 5.4, мы можем передать одно значение null параметризованному методу тестирования, используя @NullSource:

Точно так же мы можем передавать пустые значения с помощью аннотации @EmptySource :

Чтобы передать как нулевые, так и пустые значения, мы можем использовать составленную аннотацию @NullAndEmptySource :

In order to pass a few more empty string variations to the parameterized test, we can combine @ValueSource, @NullSource, and @EmptySource together:

4.3. Enum

In order to run a test with different values from an enumeration, we can use the @EnumSource annotation.

For example, we can assert that all month numbers are between 1 and 12:

Or, we can filter out a few months by using the names attribute.

How about asserting the fact that April, September, June, and November are 30 days long:

By default, the names will only keep the matched enum values. We can turn this around by setting the mode attribute to EXCLUDE:

In addition to literal strings, we can pass a regular expression to the names attribute:

Quite similar to @ValueSource, @EnumSource is only applicable when we’re going to pass just one argument per test execution.

4.4. CSV Literals

Suppose we’re going to make sure that the toUpperCase() method from String generates the expected uppercase value. @ValueSource won’t be enough.

In order to write a parameterized test for such scenarios, we have to:

So, we need argument sources capable of passing multiple arguments. The @CsvSource is one of those sources:

The @CsvSource accepts an array of comma-separated values and each array entry corresponds to a line in a CSV file.

This source takes one array entry each time, splits it by comma and passes each array to the annotated test method as separate parameters. By default, the comma is the column separator but we can customize it using the delimiter attribute:

Читайте также:  размер xxl женский это какой на русский размер одежды

Now it’s a colon-separated value, still a CSV!

4.5. CSV Files

Instead of passing the CSV values inside the code, we can refer to an actual CSV file.

For example, we could use a CSV file like:

We can load the CSV file and ignore the header column with @CsvFileSource:

The resources attribute represents the CSV file resources on the classpath to read. And, we can pass multiple files to it.

The numLinesToSkip attribute represents the number of lines to skip when reading the CSV files. By default, @CsvFileSource does not skip any lines, but this feature is usually useful for skipping the header lines, like we did here.

Just like the simple @CsvSource, the delimiter is customizable with the delimiter attribute.

In addition to the column separator:

4.6. Method

The argument sources we’ve covered so far are somewhat simple and share one limitation: It’s hard or impossible to pass complex objects using them!

One approach to providing more complex arguments is to use a method as an argument source.

Let’s test the isBlank method with a @MethodSource:

The name we supply to @MethodSource needs to match an existing method.

So let’s next write provideStringsForIsBlank, a static method that returns a Stream of Arguments:

Here we’re literally returning a stream of arguments, but it’s not a strict requirement. For example, we can return any other collection-like interfaces like List.

If we’re going to provide just one argument per test invocation, then it’s not necessary to use the Arguments abstraction:

When we don’t provide a name for the @MethodSource, JUnit will search for a source method with the same name as the test method.

Sometimes it’s useful to share arguments between different test classes. In these cases, we can refer to a source method outside of the current class by its fully-qualified name:

Using the FQN#methodName format we can refer to an external static method.

4.7. Custom Argument Provider

Another advanced approach to pass test arguments is to use a custom implementation of an interface called ArgumentsProvider:

Then we can annotate our test with the @ArgumentsSource annotation to use this custom provider:

Let’s make the custom provider a more pleasant API to use with a custom annotation!

4.8. Custom Annotation

How about loading the test arguments from a static variable? Something like:

Actually, JUnit 5 does not provide this! However, we can roll our own solution.

First off, we can create an annotation:

Then we need to somehow consume the annotation details and provide test arguments. JUnit 5 provides two abstractions to achieve those two things:

So, we next need to make the VariableArgumentsProvider class read from the specified static variable and return its value as test arguments:

And it works like a charm!

5. Argument Conversion

5.1. Implicit Conversion

Let’s re-write one of those @EnumTests with a @CsvSource:

This shouldn’t work, right? But, somehow it does!

So, JUnit 5 converts the String arguments to the specified enum type. To support use cases like this, JUnit Jupiter provides a number of built-in implicit type converters.

The conversion process depends on the declared type of each method parameter. The implicit conversion can convert the String instances to types like:

5.2. Explicit Conversion

Sometimes we need to provide a custom and explicit converter for arguments.

Suppose we want to convert strings with the yyyy/mm/dd format to LocalDate instances. First off, we need to implement the ArgumentConverter interface:

Then we should refer to the converter via the @ConvertWith annotation:

6. Argument Accessor

By default, each argument provided to a parameterized test corresponds to a single method parameter. Consequently, when passing a handful of arguments via an argument source, the test method signature gets very large and messy.

One approach to address this issue is to encapsulate all passed arguments into an instance of ArgumentsAccessor and retrieve arguments by index and type.

For example, let’s consider our Person class:

Then, in order to test the fullName() method, we’ll pass four arguments: firstName, middleName, lastName, and the expected fullName. We can use the ArgumentsAccessor to retrieve the test arguments instead of declaring them as method parameters:

Here, we’re encapsulating all passed arguments into an ArgumentsAccessor instance and then, in the test method body, retrieving each passed argument with its index. In addition to just being an accessor, type conversion is supported through get* methods:

7. Argument Aggregator

Using the ArgumentsAccessor abstraction directly may make the test code less readable or reusable. In order to address these issues, we can write a custom and reusable aggregator.

To do that, we implement the ArgumentsAggregator interface:

And then we reference it via the @AggregateWith annotation:

The PersonAggregator takes the last three arguments and instantiates a Person class out of them.

8. Customizing Display Names

By default, the display name for a parameterized test contains an invocation index along with a String representation of all passed arguments, something like:

However, we can customize this display via the name attribute of the @ParameterizedTest annotation:

April is 30 days long surely is a more readable display name:

The following placeholders are available when customizing the display name:

9. Conclusion

In this article, we’ve explored the nuts and bolts of parameterized tests in JUnit 5.

We learned that parameterized tests are different from normal tests in two aspects: they’re annotated with the @ParameterizedTest, and they need a source for their declared arguments.

Also, by now, we should now that JUnit provides some facilities to convert the arguments to custom target types or to customize the test names.

Как обычно, образцы кода доступны в нашем проекте GitHub, поэтому обязательно ознакомьтесь с ним!

Источник

Сказочный портал