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

时间:2019-09-20 10:01:52

标签: java lambda java-8 junit5

我正在学习JUnit5,但是我对函数式编程的概念分心了。 到目前为止,我可以理解为什么对于诸如dynamicTest()这样的方法,我不能使用dynamicTest(str,assertEquals(a,multiple(b,c))而不是dynamicTest(str,()-> assertEquals(a,multiple(b, c))。

“ ...,因为dynamicTest()需要执行assertEquals()作为第二个arg,而不是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 :(得分:0)

  

但是我不明白,为什么一个方法需要执行另一个方法作为其参数。我需要一个简单的例子来说明,谢谢。

也许我无法正确理解您的问题,但总之,您始终可以在参数之外执行该方法并将该输出分配给参数,例如,您又将其作为参数传递:

@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());
}

答案 1 :(得分:0)

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

  

§2.17。动态测试

     

Annotations中描述的JUnit Jupiter中的标准@Test注释与JUnit 4中的@Test注释非常相似。两者都描述了实现测试用例的方法。这些测试用例在编译时已完全指定,因此它们是静态的,并且它们的行为不能因运行时发生的任何事情而改变。 假设提供了动态行为的基本形式,但在表达方式上有意地受到限制。

     

除了这些标准测试外,JUnit Jupiter还引入了一种全新的测试编程模型。这种新型测试是动态测试,它是在运行时通过带有@TestFactory注释的工厂方法生成的。

     

@Test方法相反,@TestFactory方法本身不是测试用例,而是测试用例的工厂。因此,动态测试是工厂的产品。从技术上讲,@TestFactory方法必须返回单个DynamicNodeStreamCollectionIterableIterator或{{ 1}}个实例。 DynamicNode的可实例化子类是DynamicNodeDynamicContainerDynamicTest实例由显示名称和动态子节点列表组成,从而可以创建动态节点的任意嵌套层次结构。 DynamicContainer实例将被懒惰地执行,从而可以动态甚至不确定地生成测试用例。

     

[...]

     

DynamicTest是在运行时生成的测试用例。它由一个显示名称和一个DynamicTest组成。 Executable是可执行文件,这意味着动态测试的实现可以作为lambda表达式或方法引用提供。

     
    

动态测试生命周期

         

动态测试的执行生命周期与标准@FunctionalInterface用例的生命周期完全不同。具体来说,没有针对单个动态测试的生命周期回调。这意味着@Test@BeforeEach方法及其相应的扩展回调是针对@AfterEach方法执行的,而不是针对每个动态测试执行的。换句话说,如果您在lambda表达式中访问来自测试实例的字段以进行动态测试,则这些字段将不会被回调方法重置,也不会通过执行由同一@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中时,您将创建一个函数并将该函数传递给框架。这允许动态测试模仿非动态测试的行为,让框架在准备好执行时执行测试。

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

  1.   

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

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

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

  2.   

    以后执行的目的是什么?后来而不是立即获得的好处是什么?还在等什么方法?

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

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


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

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

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

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