Java8 - Почему методу требуется выполнение другого метода в качестве аргумента? - PullRequest
1 голос
/ 20 сентября 2019

Я изучал JUnit5, но я отвлекся от концепции функционального программирования.До сих пор я мог понять, почему для такого метода, как dynamicTest (), я не могу использовать dynamicTest (str, assertEquals (a, multiply (b, c)) вместо dynamicTest (str, () -> assertEquals (a, multiply (b,c)).

"... потому что dynamicTest () требует выполнения assertEquals () в качестве второго аргумента, а не результата assertEquals ()."

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

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

import java.util.Arrays;
import java.util.stream.Stream;

import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;

public class DynamicTestCreationTest {

    @TestFactory
    public Stream<DynamicTest> testMultiplyException() {
        MyClass tester = new MyClass();
        int[][] data = new int[][] { { 1, 2, 2 }, { 5, 3, 15 }, { 121, 4, 484 } };
        return Arrays.stream(data).map(entry -> {
            int m1 = entry[0];
            int m2 = entry[1];
            int expected = entry[2];
            return dynamicTest(m1 + " * " + m2 + " = " + expected, () -> {
                assertEquals(expected, tester.multiply(m1, m2));
            });
        });
    }

    // class to be tested
    class MyClass {
        public int multiply(int i, int j) {
            return i * j;
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 21 сентября 2019

Вот что говорит Руководство пользователя 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, вы создаете функцию и передаете ее в среду.Это позволяет динамическим тестам имитировать поведение не динамических тестов и позволяет инфраструктуре выполнять тесты, когда она готова сделать это.

Чтобы ответить на два дополнительных вопроса, вы вставили комментарий :

  1. Под «фактическим кодом, подлежащим проверке» вы подразумеваете «фактическую оценку кода, подлежащего проверке» (тесты)?потому что я думаю, что код для тестирования = multiply (x, y) вызывается немедленно, но это утверждение (), которое ждет, я прав?

    Формулировка "Код для тестирования", я теперь понимаю, неоднозначен, если не просто вводит в заблуждение.Да, я имею в виду тестовый код (т. Е. Код, заключенный в Executable, такой как утверждения) - это то, что вы не хотите вызывать немедленно, а скорее в более позднее время, когда тестовая среда готова к выполнениюthe test.

    Обратите внимание, что в вашем примере вы потенциально можете "удвоить лень" из-за использования Stream<DynamicTest>.Поскольку Stream оценивается лениво, и вы не с готовностью создаете Stream (например, с Stream.of), он создает объекты DynamicTest только по мере необходимости.Это может быть полезно, если создание DynamicTest стоит дорого, потому что можно избежать создания всех тестов заранее.Независимо от того, использует ли JUnit Jupiter это преимущество, я не уверен (не смотрел на реализацию), хотя я был бы удивлен, если бы они этого не сделали.

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

    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?

  • Параметризованный тест проходит через обычный жизненный цикл для каждого вызова .
  • СТестовая фабрика, динамически генерируется весь тест, а не только параметры.Вероятно, вы могли бы подражать этому с помощью параметризованного теста, но вы бы боролись против дизайна.
  • С фабрикой тестов вы буквально создаете тесты ;параметризованный тест уже существует, вы просто предоставляете разные параметры для каждого вызова.

По крайней мере, так я понимаю различия.

0 голосов
/ 20 сентября 2019

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

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

@Test
public void test_oddMethod() {
    OddClass oddClass = new OddClass();
    boolean expected = Boolean.TRUE;
    boolean methodOutput = oddClass.isOdd();
    assertEquals(expected, methodOutput);
}

Одна из причин, по которой вы выполняете вызов метода как часть параметра, заключается в простом сокращении строккод и сделать ваш метод более «читабельным».В приведенном выше примере нет реальной причины объявлять логическое значение methodOutput , поскольку оно используется только один раз как часть assertEquals (...) , поэтому это можно упростить до:

@Test
public void test_oddMethod() {
    OddClass oddClass = new OddClass();
    boolean expected = Boolean.TRUE;
    assertEquals(expected, oddClass.isOdd());
}

Вы можете упростить это еще больше:

@Test
public void test_oddMethod() {
    assertEquals(Boolean.TRUE, new OddClass().isOdd());
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...