java - Java8-为什么一个方法需要执行另一个方法作为其参数?

标签 java lambda java-8 junit5

我当时正在学习JUnit5,但是我对函数式编程的概念分神了。
到目前为止,我能理解为什么对于诸如dynamicTest()这样的方法,我不能使用dynamicTest(str,assertEquals(a,multiple(b,c))而不是dynamicTest(str,()-> assertEquals(a,multiple(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;
        }
    }
}

最佳答案

这是《 JUnit 5用户指南》关于Dynamic Tests的内容:


  第2.17节。动态测试
  
  Annotations中描述的JUnit Jupiter中的标准@Test注释与JUnit 4中的@Test注释非常相似。两者都描述了实现测试用例的方法。这些测试用例在编译时已完全指定,因此它们是静态的,并且它们的行为不能因运行时发生的任何事情而改变。假设提供了动态行为的基本形式,但故意在表现力上受到限制。
  
  除了这些标准测试外,JUnit Jupiter还引入了一种全新的测试编程模型。这种新型测试是一种动态测试,它是在运行时通过带有@TestFactory注释的工厂方法生成的。
  
  与@Test方法相反,@TestFactory方法本身不是测试用例,而是测试用例的工厂。因此,动态测试是工厂的产品。从技术上讲,@TestFactory方法必须返回单个DynamicNodeStreamCollectionIterableIteratorDynamicNode实例数组。 DynamicNode的可实例化子类是DynamicContainerDynamicTestDynamicContainer实例由显示名称和动态子节点列表组成,从而可以创建动态节点的任意嵌套的层次结构。 DynamicTest实例将延迟执行,从而能够动态生成甚至不确定的测试用例。
  
  [...]
  
  DynamicTest是在运行时生成的测试用例。它由显示名称和Executable组成。可执行文件是@FunctionalInterface,这意味着动态测试的实现可以作为lambda表达式或方法引用提供。
  
  
    动态测试生命周期
    
    动态测试的执行生命周期与标准@Test情况的执行生命周期完全不同。具体来说,没有针对单个动态测试的生命周期回调。这意味着@BeforeEach@AfterEach方法及其相应的扩展回调将为@TestFactory方法执行,但不会为每个动态测试执行。换句话说,如果您在lambda表达式中访问来自测试实例的字段以进行动态测试,则这些字段将不会由回调方法或由同一@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中时,您将创建一个函数并将该函数传递给框架。这允许动态测试模仿非动态测试的行为,让框架在准备就绪时执行测试。

要回答您的两个附加问题,请输入a comment



  “实际要测试的代码”是指“实际要测试的代码”(测试)吗?因为我认为要测试的代码= Multiple(x,y)被立即调用,但是等待的是assertion(),对吗?


我现在意识到,“要测试的代码”的措词是模棱两可的,即使不仅仅是误导。是的,我的意思是测试代码(即包装在Executable中的代码,例如断言)是您不希望立即调用的代码,而是稍后的时间(当测试框架准备执行时)考试。

请注意,由于使用了Stream<DynamicTest>,您的示例中可能有“懒惰的两倍”的可能。由于Stream的计算是延迟的,并且您不急于构建Stream(例如,使用Stream.of),因此它仅在需要时创建DynamicTest对象。如果创建DynamicTest昂贵,这将是有益的,因为可以避免预先创建所有测试。我不确定JUnit Jupiter是否利用了这一点,但我不确定(没有研究实现),但是如果他们不这样做,我会感到惊讶。

  之后执行的意义是什么?后来而不是立即获得的好处是什么?还等什么方法?


DynamicTest等待传递给框架,然后等待框架执行(并且执行DynamicTest涉及执行Executable)。

记住,我们在这里与测试工厂打交道,这意味着您正在创建测试而不执行测试。执行测试是框架的责任。如果Executable急切地执行,那就是您执行测试而不是框架。实际上,急切的执行会将每个DynamicTest隐藏在@TestFactory方法内,从而防止框架将其视为单独的测试。框架必须知道它正在执行哪个测试才能给出准确的报告。另外,如果急于执行,则测试失败将阻止执行任何剩余的测试。




请注意,您问题中的示例也可以通过parameterized test完成。

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有什么区别?


参数化测试进入through the normal lifecycle for each invocation
使用测试工厂,可以动态生成整个测试,而不仅仅是参数。您可能可以通过参数化测试来模仿这一点,但是您会与设计抗衡。
使用测试工厂,您实际上是在创建测试。参数化测试已经存在,您只需为每次调用提供不同的参数。


至少,这就是我理解差异的方式。

关于java - Java8-为什么一个方法需要执行另一个方法作为其参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58026124/

相关文章:

java - 带有 JTextField 和 JButton 的 GridbagLayout,当 JButton 可见时重新格式化

java 8 流 : grouping by and storing sum in new object, 和合并映射

python - Pandas 群体内的变量转移

c# - 重用 lambda 表达式

java - 如何使用 Java 8 流获取每个不同键的第一个值?

java - 在没有 Leiningen 的情况下在 Clojure 中包含文件 : Could not locate file

java - 连接到 HP 质量中心 alm(版本 12.50)

java - 计算屏幕上的输出出现次数

java-8 - jetty 9 + JDK 8 + spring 4 注解

java - Consumer 和 Then 方法如何在 Java 函数式接口(interface)中内部工作