Java8 - 为什么一个方法需要执行另一个方法作为它的参数?
Java8 - Why a method would need the execution of another method as its argument?
我正在学习 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;
}
}
}
But I cant understand, why a method would need the execution of another method as its argument. I would need an explanation with a simple example, thanks.
也许我没有正确理解你的问题,但简而言之,你总是可以在参数之外执行该方法并将该输出分配给一个参数,你又将其作为参数传递,例如:
@Test
public void test_oddMethod() {
OddClass oddClass = new OddClass();
boolean expected = Boolean.TRUE;
boolean methodOutput = oddClass.isOdd();
assertEquals(expected, methodOutput);
}
将方法调用作为参数的一部分执行的一个原因是简单地减少代码行数并使您的方法更多 "readable"。在上面的示例中,没有真正的理由声明布尔值 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());
}
这是 JUnit 5 用户指南 对 Dynamic Tests 的评价:
§2.17. Dynamic Tests
The standard @Test
annotation in JUnit Jupiter described in Annotations is very similar to the @Test
annotation in JUnit 4. Both describe methods that implement test cases. These test cases are static in the sense that they are fully specified at compile time, and their behavior cannot be changed by anything happening at runtime. Assumptions provide a basic form of dynamic behavior but are intentionally rather limited in their expressiveness.
In addition to these standard tests a completely new kind of test programming model has been introduced in JUnit Jupiter. This new kind of test is a dynamic test which is generated at runtime by a factory method that is annotated with @TestFactory
.
In contrast to @Test
methods, a @TestFactory
method is not itself a test case but rather a factory for test cases. Thus, a dynamic test is the product of a factory. Technically speaking, a @TestFactory
method must return a single DynamicNode
or a Stream
, Collection
, Iterable
, Iterator
, or array of DynamicNode
instances. Instantiable subclasses of DynamicNode
are DynamicContainer
and DynamicTest
. DynamicContainer
instances are composed of a display name and a list of dynamic child nodes, enabling the creation of arbitrarily nested hierarchies of dynamic nodes. DynamicTest
instances will be executed lazily, enabling dynamic and even non-deterministic generation of test cases.
[...]
A DynamicTest
is a test case generated at runtime. It is composed of a display name and an Executable
. Executable is a @FunctionalInterface
which means that the implementations of dynamic tests can be provided as lambda expressions or method references.
Dynamic Test Lifecycle
The execution lifecycle of a dynamic test is quite different than it is for a standard @Test
case. Specifically, there are no lifecycle callbacks for individual dynamic tests. This means that @BeforeEach
and @AfterEach
methods and their corresponding extension callbacks are executed for the @TestFactory
method but not for each dynamic test. In other words, if you access fields from the test instance within a lambda expression for a dynamic test, those fields will not be reset by callback methods or extensions between the execution of individual dynamic tests generated by the same @TestFactory
method.
[...]
如前所述,动态测试在运行时生成并由 DynamicTest
对象表示。这意味着当您使用 @TestFactory
方法时,您是在 创建测试 ,而不是执行它们。为了支持延迟执行,您需要将实际测试封装在一个对象中,这是通过 Executable
完成的。将单个 DynamicTest
想象成 "normal" @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
中时,您正在创建一个函数并将该函数传递给框架。这允许动态测试模仿非动态测试的行为,让框架在准备好执行测试时执行测试。
要回答您在 中提出的另外两个问题:
By "actual code-to-be-tested" do you mean "actual code-to-be-tested-evaluation" (tests)? because i think code-to-be-tested = multiply(x,y) is invoked immediately , but it's the assertion() which waits, am I right?
“要测试的代码”的措辞,我现在意识到,即使不只是误导,也是模棱两可的。是的,我的意思是测试代码(即包装在 Executable
中的代码,例如断言)是您不希望立即调用的代码,而是稍后调用的代码——当测试框架准备就绪时执行测试。
请注意,由于使用 Stream<DynamicTest>
,您的示例中可能有 "double the laziness"。由于 Stream
是延迟计算的,并且您不会急切地构建 Stream
(例如使用 Stream.of
),它只会在需要时创建 DynamicTest
对象。如果创建 DynamicTest
很昂贵,这可能是有益的,因为可以避免预先创建所有测试。 JUnit Jupiter 是否利用了这一点,我不确定(没有看过实现),但如果他们没有,我会感到惊讶。
And what is the point of executing-later? what is the advantage of later and not immediately? what are the method waiting for?
DynamicTest
正在等待传递给框架,然后等待框架执行它(执行DynamicTest
涉及执行Executable
)。
请记住,我们在这里处理测试 工厂,这意味着您正在 创建测试 而不是 执行测试。执行测试是框架的责任。如果 Executable
被急切地执行,那么它会是 you 执行测试而不是框架。实际上,急切执行会将每个 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。
- 有了测试工厂,可以动态生成整个测试,而不仅仅是参数。您可能可以通过参数化测试来模仿这一点,但您会反对设计。
- 有了测试工厂,您实际上就是在创建测试;参数化测试已经存在,您只需为每次调用提供不同的参数。
至少,我是这样理解差异的。
我正在学习 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;
}
}
}
But I cant understand, why a method would need the execution of another method as its argument. I would need an explanation with a simple example, thanks.
也许我没有正确理解你的问题,但简而言之,你总是可以在参数之外执行该方法并将该输出分配给一个参数,你又将其作为参数传递,例如:
@Test
public void test_oddMethod() {
OddClass oddClass = new OddClass();
boolean expected = Boolean.TRUE;
boolean methodOutput = oddClass.isOdd();
assertEquals(expected, methodOutput);
}
将方法调用作为参数的一部分执行的一个原因是简单地减少代码行数并使您的方法更多 "readable"。在上面的示例中,没有真正的理由声明布尔值 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());
}
这是 JUnit 5 用户指南 对 Dynamic Tests 的评价:
§2.17. Dynamic Tests
The standard
@Test
annotation in JUnit Jupiter described in Annotations is very similar to the@Test
annotation in JUnit 4. Both describe methods that implement test cases. These test cases are static in the sense that they are fully specified at compile time, and their behavior cannot be changed by anything happening at runtime. Assumptions provide a basic form of dynamic behavior but are intentionally rather limited in their expressiveness.In addition to these standard tests a completely new kind of test programming model has been introduced in JUnit Jupiter. This new kind of test is a dynamic test which is generated at runtime by a factory method that is annotated with
@TestFactory
.In contrast to
@Test
methods, a@TestFactory
method is not itself a test case but rather a factory for test cases. Thus, a dynamic test is the product of a factory. Technically speaking, a@TestFactory
method must return a singleDynamicNode
or aStream
,Collection
,Iterable
,Iterator
, or array ofDynamicNode
instances. Instantiable subclasses ofDynamicNode
areDynamicContainer
andDynamicTest
.DynamicContainer
instances are composed of a display name and a list of dynamic child nodes, enabling the creation of arbitrarily nested hierarchies of dynamic nodes.DynamicTest
instances will be executed lazily, enabling dynamic and even non-deterministic generation of test cases.[...]
A
DynamicTest
is a test case generated at runtime. It is composed of a display name and anExecutable
. Executable is a@FunctionalInterface
which means that the implementations of dynamic tests can be provided as lambda expressions or method references.Dynamic Test Lifecycle
The execution lifecycle of a dynamic test is quite different than it is for a standard
@Test
case. Specifically, there are no lifecycle callbacks for individual dynamic tests. This means that@BeforeEach
and@AfterEach
methods and their corresponding extension callbacks are executed for the@TestFactory
method but not for each dynamic test. In other words, if you access fields from the test instance within a lambda expression for a dynamic test, those fields will not be reset by callback methods or extensions between the execution of individual dynamic tests generated by the same@TestFactory
method.[...]
如前所述,动态测试在运行时生成并由 DynamicTest
对象表示。这意味着当您使用 @TestFactory
方法时,您是在 创建测试 ,而不是执行它们。为了支持延迟执行,您需要将实际测试封装在一个对象中,这是通过 Executable
完成的。将单个 DynamicTest
想象成 "normal" @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
中时,您正在创建一个函数并将该函数传递给框架。这允许动态测试模仿非动态测试的行为,让框架在准备好执行测试时执行测试。
要回答您在
By "actual code-to-be-tested" do you mean "actual code-to-be-tested-evaluation" (tests)? because i think code-to-be-tested = multiply(x,y) is invoked immediately , but it's the assertion() which waits, am I right?
“要测试的代码”的措辞,我现在意识到,即使不只是误导,也是模棱两可的。是的,我的意思是测试代码(即包装在
Executable
中的代码,例如断言)是您不希望立即调用的代码,而是稍后调用的代码——当测试框架准备就绪时执行测试。请注意,由于使用
Stream<DynamicTest>
,您的示例中可能有 "double the laziness"。由于Stream
是延迟计算的,并且您不会急切地构建Stream
(例如使用Stream.of
),它只会在需要时创建DynamicTest
对象。如果创建DynamicTest
很昂贵,这可能是有益的,因为可以避免预先创建所有测试。 JUnit Jupiter 是否利用了这一点,我不确定(没有看过实现),但如果他们没有,我会感到惊讶。And what is the point of executing-later? what is the advantage of later and not immediately? what are the method waiting for?
DynamicTest
正在等待传递给框架,然后等待框架执行它(执行DynamicTest
涉及执行Executable
)。请记住,我们在这里处理测试 工厂,这意味着您正在 创建测试 而不是 执行测试。执行测试是框架的责任。如果
Executable
被急切地执行,那么它会是 you 执行测试而不是框架。实际上,急切执行会将每个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。
- 有了测试工厂,可以动态生成整个测试,而不仅仅是参数。您可能可以通过参数化测试来模仿这一点,但您会反对设计。
- 有了测试工厂,您实际上就是在创建测试;参数化测试已经存在,您只需为每次调用提供不同的参数。
至少,我是这样理解差异的。