Главная страница

Basic Java Tools for Building & Testing Apps


Скачать 4.76 Mb.
НазваниеBasic Java Tools for Building & Testing Apps
АнкорJUNIt
Дата14.11.2022
Размер4.76 Mb.
Формат файлаpdf
Имя файла5_2_0_Maven_JUnit_Tutorial.pdf
ТипРеферат
#787190
страница14 из 16
1   ...   8   9   10   11   12   13   14   15   16
@Test
@EnabledIf
(
"customCondition"
)
void
enabled
() {
// ...
}
@Test
@DisabledIf
(
"customCondition"
)
void
disabled
() {
// ...
}
boolean
customCondition
() {
return true;
}
Parameterized Tests
Parameterized tests make it possible to run a test multiple times with different arguments. They are declared just like regular
@Test methods but use the
@ParameterizedTest annotation instead. In addition, you must declare at least one source that will provide the arguments for each invocation and then consume the arguments in the test method.
The following example demonstrates a parameterized test that uses the
@ValueSource annotation to specify a
String array as the source of arguments.
@ParameterizedTest
@ValueSource
(strings = {
"racecar"
,
"radar"
,
"able was I ere I saw elba"
})
void
palindromes
(
String
candidate) { assertTrue(
StringUtils
.
isPalindrome
(candidate));
}
When executing the above parameterized test method, each invocation will be reported separately. For instance, the
ConsoleLauncher will print output similar to the following. palindromes(String)

├─ [1] candidate=racecar

├─ [2] candidate=radar

└─ [3] candidate=able was I ere I saw elba

Parameterized tests are currently an experimental feature. Consult the table in
Experimental APIs for details.
2.15.1. Required Setup
In order to use parameterized tests you need to add a dependency on the junit-jupiter-params artifact. Please refer to
Dependency
Metadata for details.
2.15.2. Consuming Arguments

Parameterized test methods typically consume arguments directly from the configured source (see
Sources of Arguments
) following a one-to- one correlation between argument source index and method parameter index (see examples in
@CsvSource
). However, a parameterized test method may also choose to aggregate arguments from the source into a single object passed to the method (see
Argument Aggregation
).
Additional arguments may also be provided by a
ParameterResolver
(e.g., to obtain an instance of
TestInfo
,
TestReporter
, etc.).
Specifically, a parameterized test method must declare formal parameters according to the following rules.

Zero or more indexed arguments must be declared first.

Zero or more aggregators must be declared next.

Zero or more arguments supplied by a
ParameterResolver must be declared last.
In this context, an indexed argument is an argument for a given index in the
Arguments provided by an
ArgumentsProvider that is passed as an argument to the parameterized method at the same index in the method’s formal parameter list. An aggregator is any parameter of type
ArgumentsAccessor or any parameter annotated with
@AggregateWith
2.15.3. Sources of Arguments
Out of the box, JUnit Jupiter provides quite a few source annotations. Each of the following subsections provides a brief overview and an example for each of them. Please refer to the Javadoc in the org.junit.jupiter.params.provider package for additional information.
@ValueSource
@ValueSource is one of the simplest possible sources. It lets you specify a single array of literal values and can only be used for providing a single argument per parameterized test invocation.
The following types of literal values are supported by
@ValueSource

short

byte

int

long

float

double

char

boolean

java.lang.String

java.lang.Class
For example, the following
@ParameterizedTest method will be invoked three times, with the values
1
,
2
, and
3
respectively.
@ParameterizedTest
@ValueSource
(ints = {
1
,
2
,
3
})
void
testWithValueSource
(
int
argument) { assertTrue(argument >
0
&& argument <
4
);
}
Null and Empty Sources
In order to check corner cases and verify proper behavior of our software when it is supplied bad input, it can be useful to have null and empty values supplied to our parameterized tests. The following annotations serve as sources of null and empty values for parameterized tests that accept a single argument.

@NullSource
: provides a single null argument to the annotated
@ParameterizedTest method. o
@NullSource cannot be used for a parameter that has a primitive type.

@EmptySource
: provides a single empty argument to the annotated
@ParameterizedTest method for parameters of the following types: java.lang.String
, java.util.List
, java.util.Set
, java.util.Map
, primitive arrays (e.g., int[]
, char[][]
, etc.), object arrays (e.g.,
String[]
,
Integer[][]
, etc.).
o
Subtypes of the supported types are not supported.

@NullAndEmptySource
: a composed annotation that combines the functionality of
@NullSource and
@EmptySource
If you need to supply multiple varying types of blank strings to a parameterized test, you can achieve that using
@ValueSource
— for example,
@ValueSource(strings = {" ", " ", "\t", "\n"})
You can also combine
@NullSource
,
@EmptySource
, and
@ValueSource to test a wider range of null
, empty, and blank input. The following example demonstrates how to achieve this for strings.
@ParameterizedTest
@NullSource
@EmptySource
@ValueSource
(strings = {
" "
,
" "
,
"\t"
,
"\n"
})
void
nullEmptyAndBlankStrings
(
String
text) { assertTrue(text == null || text.
trim
().
isEmpty
());
}
Making use of the composed
@NullAndEmptySource annotation simplifies the above as follows.
@ParameterizedTest
@NullAndEmptySource
@ValueSource
(strings = {
" "
,
" "
,
"\t"
,
"\n"
})
void
nullEmptyAndBlankStrings
(
String
text) { assertTrue(text == null || text.
trim
().
isEmpty
());
}
Both variants of the nullEmptyAndBlankStrings(String)
parameterized test method result in six invocations: 1 for null
, 1 for the empty string, and 4 for the explicit blank strings supplied via
@ValueSource
@EnumSource
@EnumSource provides a convenient way to use
Enum constants.
@ParameterizedTest
@EnumSource
(
ChronoUnit
.
class
)
void
testWithEnumSource
(
TemporalUnit
unit) { assertNotNull(unit);
}
The annotation’s value attribute is optional. When omitted, the declared type of the first method parameter is used. The test will fail if it does not reference an enum type. Thus, the value attribute is required in the above example because the method parameter is declared as
TemporalUnit
, i.e. the interface implemented by
ChronoUnit
, which isn’t an enum type. Changing the method parameter type to
ChronoUnit allows you to omit the explicit enum type from the annotation as follows.
@ParameterizedTest
@EnumSource
void
testWithEnumSourceWithAutoDetection
(
ChronoUnit
unit) { assertNotNull(unit);
}
The annotation provides an optional names attribute that lets you specify which constants shall be used, like in the following example. If omitted, all constants will be used.
@ParameterizedTest
@EnumSource
(names = {
"DAYS"
,
"HOURS"
})
void
testWithEnumSourceInclude
(
ChronoUnit
unit) { assertTrue(
EnumSet
.
of
(
ChronoUnit
.
DAYS
,
ChronoUnit
.
HOURS
).
contains
(unit));
}
The
@EnumSource annotation also provides an optional mode attribute that enables fine-grained control over which constants are passed to the test method. For example, you can exclude names from the enum constant pool or specify regular expressions as in the following examples.
@ParameterizedTest

@EnumSource
(mode =
EXCLUDE
, names = {
"ERAS"
,
"FOREVER"
})
void
testWithEnumSourceExclude
(
ChronoUnit
unit) { assertFalse(
EnumSet
.
of
(
ChronoUnit
.
ERAS
,
ChronoUnit
.
FOREVER
).
contains
(unit));
}
@ParameterizedTest
@EnumSource
(mode =
MATCH_ALL
, names =
"^.*DAYS$"
)
void
testWithEnumSourceRegex
(
ChronoUnit
unit) { assertTrue(unit.
name
().
endsWith
(
"DAYS"
));
}
@MethodSource
@MethodSource allows you to refer to one or more factory methods of the test class or external classes.
Factory methods within the test class must be static unless the test class is annotated with
@TestInstance(Lifecycle.PER_CLASS)
; whereas, factory methods in external classes must always be static
. In addition, such factory methods must not accept any arguments.
Each factory method must generate a stream of arguments, and each set of arguments within the stream will be provided as the physical arguments for individual invocations of the annotated
@ParameterizedTest method. Generally speaking this translates to a
Stream of
Arguments
(i.e.,
Stream
); however, the actual concrete return type can take on many forms. In this context, a
"stream" is anything that JUnit can reliably convert into a
Stream
, such as
Stream
,
DoubleStream
,
LongStream
,
IntStream
,
Collection
,
Iterator
,
Iterable
, an array of objects, or an array of primitives. The "arguments" within the stream can be supplied as an instance of
Arguments
, an array of objects (e.g.,
Object[]
), or a single value if the parameterized test method accepts a single argument.
If you only need a single parameter, you can return a
Stream of instances of the parameter type as demonstrated in the following example.
@ParameterizedTest
@MethodSource
(
"stringProvider"
)
void
testWithExplicitLocalMethodSource
(
String
argument) { assertNotNull(argument);
}
static
Stream
<
String
>
stringProvider
() {
return
Stream
.
of
(
"apple"
,
"banana"
);
}
If you do not explicitly provide a factory method name via
@MethodSource
, JUnit Jupiter will search for a factory method that has the same name as the current
@ParameterizedTest method by convention. This is demonstrated in the following example.
@ParameterizedTest
@MethodSource
void
testWithDefaultLocalMethodSource
(
String
argument) { assertNotNull(argument);
}
static
Stream
<
String
>
testWithDefaultLocalMethodSource
() {
return
Stream
.
of
(
"apple"
,
"banana"
);
}
Streams for primitive types (
DoubleStream
,
IntStream
, and
LongStream
) are also supported as demonstrated by the following example.
@ParameterizedTest
@MethodSource
(
"range"
)
void
testWithRangeMethodSource
(
int
argument) { assertNotEquals(
9
, argument);
}
static
IntStream
range
() {
return
IntStream
.
range
(
0
,
20
).
skip
(
10
);
}
If a parameterized test method declares multiple parameters, you need to return a collection, stream, or array of
Arguments instances or object arrays as shown below (see the Javadoc for
@MethodSource for further details on supported return types). Note that arguments(Object…)
is a static factory method defined in the
Arguments interface. In addition,
Arguments.of(Object…)
may be used as an alternative to arguments(Object…)

@ParameterizedTest
@MethodSource
(
"stringIntAndListProvider"
)
void
testWithMultiArgMethodSource
(
String
str,
int
num,
List
<
String
> list) { assertEquals(
5
, str.
length
()); assertTrue(num >=
1
&& num <=
2
); assertEquals(
2
, list.
size
());
}
static
Stream
<
Arguments
>
stringIntAndListProvider
() {
return
Stream
.
of
( arguments(
"apple"
,
1
,
Arrays
.
asList
(
"a"
,
"b"
)), arguments(
"lemon"
,
2
,
Arrays
.
asList
(
"x"
,
"y"
))
);
}
An external, static
factory method can be referenced by providing its fully qualified method name as demonstrated in the following example.
package example
;
import java.util.stream.Stream
;
import org.junit.jupiter.params.ParameterizedTest
;
import org.junit.jupiter.params.provider.MethodSource
;
class
ExternalMethodSourceDemo
{
@ParameterizedTest
@MethodSource
(
"example.StringsProviders#tinyStrings"
)
void
testWithExternalMethodSource
(
String
tinyString) {
// test with tiny string
}
}
class
StringsProviders
{
static
Stream
<
String
>
tinyStrings
() {
return
Stream
.
of
(
"."
,
"oo"
,
"OOO"
);
}
}

Передовой опыт тестирования в Java
Автор оригинала: Philipp Hauer

Блог компании FunCorp
,

Java
,

Тестирование веб-сервисов
,

Kotlin

Перевод
Чтобы покрытие кода было достаточным, а создание нового функционала и рефакторинг старого проходили без страха что-то сломать, тесты должны быть поддерживаемыми и легко читаемыми.
В этой статье я расскажу о множестве приёмов написания юнит- и интеграционных тестов на Java, собранных мной за несколько лет. Я буду опираться на современные технологии: JUnit5, AssertJ,
Testcontainers, а также не обойду вниманием Kotlin. Некоторые советы покажутся вам очевидными, другие могут идти вразрез с тем, что вы читали в книгах о разработке ПО и тестировании.
Вкратце

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

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

Пишите прямолинейные тесты, чтобы не переиспользовать продакшн-код, сравнивайте выдачу тестируемых методов с константами прямо в коде теста

KISS важнее DRY

Запускайте тесты в среде, максимально похожей на боевую, тестируйте максимально полную связку компонентов, не используйте in-memory-базы данных

JUnit5 и AssertJ — очень хороший выбор

Вкладывайтесь в простоту тестирования: избегайте статических свойств и методов, используйте внедрение в конструкторы, используйте экземпляры класса Clock и отделяйте бизнес-логику от асинхронной.
Общие положения
Given, When, Then (Дано, Когда, То)
Тест должен содержать три блока, разделённых пустыми строками. Каждый блок должен быть максимально коротким. Используйте локальные методы для компактности записи.
Given / Дано (ввод): подготовка теста, например, создание данных и конфигурация моков.
When / Когда (действие): вызов тестируемого метода
Then / То (вывод): проверка корректности полученного значения

// Правильно
@Test public void findProduct
() {
insertIntoDatabase(
new
Product(
100
,
"Smartphone"
));
Product product = dao.findProduct(
100
);
assertThat(product.getName()).isEqualTo(
"Smartphone"
);
}
1   ...   8   9   10   11   12   13   14   15   16


написать администратору сайта