Вот что говорит Руководство пользователя JUnit 5 о Динамических тестах :
§2.17.Динамические тесты
Стандартная аннотация @Test
в JUnit Jupiter, описанная в Annotations , очень похожа на аннотацию @Test
в JUnit 4. Оба описывают методы, реализующие тестовые случаи.Эти тестовые примеры являются статическими в том смысле, что они полностью определены во время компиляции, и их поведение не может быть изменено ничем, происходящим во время выполнения. Допущения обеспечивают базовую форму динамического поведения, но намеренно довольно ограничены по своей выразительности.
В дополнение к этим стандартным тестам в JUnit Jupiter был введен совершенно новый тип тестовой модели программирования.Этот новый вид теста является динамическим тестом , который генерируется во время выполнения заводским методом, аннотированным @TestFactory
.
В отличие от @Test
методов, @TestFactory
Метод не сам по себе тестовый пример, а скорее фабрика для тестовых случаев.Таким образом, динамический тест является продуктом фабрики.Технически говоря, метод @TestFactory
должен возвращать один DynamicNode
или Stream
, Collection
, Iterable
, Iterator
или массив DynamicNode
экземпляров.Возможные подклассы DynamicNode
: DynamicContainer
и DynamicTest
.DynamicContainer
экземпляры состоят из отображаемого имени и списка динамических дочерних узлов, что позволяет создавать произвольно вложенные иерархии динамических узлов.DynamicTest
экземпляры будут выполняться лениво, обеспечивая динамическую и даже недетерминированную генерацию тестовых случаев.
[...]
A DynamicTest
- это тестовый случай, созданный во время выполнения.Он состоит из отображаемого имени и Executable
.Исполняемый файл представляет собой @FunctionalInterface
, что означает, что реализации динамических тестов могут быть предоставлены как лямбда-выражения или ссылки на методы.
Динамический тест жизненного цикла
Жизненный цикл выполнения динамического теста сильно отличается от стандартного @Test
случая.В частности, нет никаких обратных вызовов жизненного цикла для отдельных динамических тестов.Это означает, что методы @BeforeEach
и @AfterEach
и соответствующие им обратные вызовы расширений выполняются для метода @TestFactory
, но не для каждого динамического теста .Другими словами, если вы обращаетесь к полям из экземпляра теста в лямбда-выражении для динамического теста, эти поля не будут сброшены методами обратного вызова или расширениями между выполнением отдельных динамических тестов, созданных одним и тем же методом @TestFactory
.
[...]
Как объяснено, динамический тест генерируется во время выполнения и представляется объектом DynamicTest
.Это означает, что когда у вас есть метод @TestFactory
, вы создаете тесты , а не выполняете их.Для поддержки отложенного выполнения вам необходимо инкапсулировать фактический тест в объекте, что делается с помощью Executable
.Это может помочь представить один DynamicTest
как «нормальный» @Test
.Скажем, у вас есть:
@TestFactory
DynamicTest generateDynamicTest() {
return DynamicTest.dynamicTest(
"2 + 2 = 4",
() -> assertEquals(4, 2 + 2, "the world is burning")
);
}
В качестве метода @Test
вышеприведенное выглядит следующим образом:
@Test
@DisplayName("2 + 2 = 4")
void testMath() {
assertEquals(4, 2 + 2, "the world is burning");
}
Примечание: Два не совсемэквивалент.Как упомянуто в руководстве пользователя, динамические тесты не имеют тот же жизненный цикл, что и обычные @Test
методы - прочитайте руководство, чтобы понять различия.
Другими словами, Executable
- это телометода испытаний.Вы можете думать о @TestFactory
как о генерации нескольких методов тестирования во время выполнения (концептуально).Поэтому, когда вы помещаете свой тестовый код в Executable
, вы создаете функцию и передаете ее в среду.Это позволяет динамическим тестам имитировать поведение не динамических тестов и позволяет инфраструктуре выполнять тесты, когда она готова сделать это.
Чтобы ответить на два дополнительных вопроса, вы вставили комментарий :
Под «фактическим кодом, подлежащим проверке» вы подразумеваете «фактическую оценку кода, подлежащего проверке» (тесты)?потому что я думаю, что код для тестирования = multiply (x, y) вызывается немедленно, но это утверждение (), которое ждет, я прав?
Формулировка "Код для тестирования", я теперь понимаю, неоднозначен, если не просто вводит в заблуждение.Да, я имею в виду тестовый код (т. Е. Код, заключенный в Executable
, такой как утверждения) - это то, что вы не хотите вызывать немедленно, а скорее в более позднее время, когда тестовая среда готова к выполнениюthe test.
Обратите внимание, что в вашем примере вы потенциально можете "удвоить лень" из-за использования Stream<DynamicTest>
.Поскольку Stream
оценивается лениво, и вы не с готовностью создаете Stream
(например, с Stream.of
), он создает объекты DynamicTest
только по мере необходимости.Это может быть полезно, если создание DynamicTest
стоит дорого, потому что можно избежать создания всех тестов заранее.Независимо от того, использует ли JUnit Jupiter это преимущество, я не уверен (не смотрел на реализацию), хотя я был бы удивлен, если бы они этого не сделали.
И какой смысл исполнять позже?в чем преимущество позже и не сразу?чего ожидает метод?
DynamicTest
ожидает передачи в платформу и затем ждет, пока каркас выполнит ее (а выполнение DynamicTest
включает в себя выполнение Executable
).
Помните, мы имеем дело с тестом factory , что означает, что вы создаете тесты , а не выполняете тесты ,Выполнение тестов является обязанностью фреймворка.Если Executable
был выполнен с нетерпением, то вы выполняете тест вместо фреймворка.По сути, нетерпеливое выполнение будет скрывать каждый DynamicTest
внутри метода @TestFactory
, не позволяя инфраструктуре рассматривать их как отдельные тесты;фреймворк должен знать, какой тест он выполняет, чтобы дать точный отчет.Кроме того, в случае успешного выполнения тестовый сбой не позволит выполнить оставшиеся тесты.
Обратите внимание, что пример в вашем вопросе также может быть выполнен с параметризованным тестом ..
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
class MultiplicationTests {
static Stream<Integer[]> numbersProvider() {
return Stream.of(
new Integer[]{1, 2, 2},
new Integer[]{5, 3, 15},
new Integer[]{121, 4, 484}
);
}
@ParameterizedTest(name = "{0} * {1} = {2}")
@MethodSource("numbersProvider")
void testMultiplication(int a, int b, int expectedResult) {
assertEquals(expectedResult, a * b);
}
}
Конечно, это просто еще один способ сделать то же самое.Так в чем же разница между @ParameterizedTest
и @TestFactory
?
- Параметризованный тест проходит через обычный жизненный цикл для каждого вызова .
- СТестовая фабрика, динамически генерируется весь тест, а не только параметры.Вероятно, вы могли бы подражать этому с помощью параметризованного теста, но вы бы боролись против дизайна.
- С фабрикой тестов вы буквально создаете тесты ;параметризованный тест уже существует, вы просто предоставляете разные параметры для каждого вызова.
По крайней мере, так я понимаю различия.