junit5
回顾过去
使用JUnit 4,我们可以选择使用自定义JUnit运行器(由@RunWith批注指示)运行测试。 这使我们能够修改使用JUnit执行测试的方式。 但是,JUnit运行程序并不是那么容易实现。 它们还受到主要限制,即只能在测试中使用一个跑步者。
在JUnit 4.7中引入了规则。 规则使用不同的概念来自定义测试。 一个测试中也可以使用多个规则。 因此,从这一点开始,JUnit 4具有两种不同的方式(具有不同的向上和向下)来自定义测试行为。
JUnit 5引入了扩展
引入扩展的JUnit 5改变了整个定制机制。 扩展可以通过多种方式添加到测试中。 最常见的方法是@ExtendWith批注,可用于测试类或单个测试方法。 例如:
@ExtendWith (MyFirstExtension. class )
public class DemoTest {
@Test
public void test() {
// uses MyFirstExtension
}
@Test
@ExtendWith (MySecondExtension. class )
public void test2() {
// uses MyFirstExtension and MySecondExtension
}
}
添加到测试类的扩展将用于该类中的所有测试方法。
可以通过传递一组扩展名来注册多个扩展名:
@ExtendWith ({ MyFirstExtension. class , MySecondExtension. class })
public class DemoTest {
...
}
@ExtendWith也是可重复的注释,因此可以多次添加:
@ExtendWith (MyFirstExtension. class )
@ExtendWith (MySecondExtension. class )
public class DemoTest {
...
}
请注意,@ ExtendWith可以组成其他注释。 例如,我们可以使用@ExtendWith来注释自己的注释:
@Retention (RetentionPolicy.RUNTIME)
@ExtendWith (MockWebServerExtension. class )
@ExtendWith (MockDatabaseExtension. class )
@Target (ElementType.TYPE)
public @interface IntegrationTest {}
现在,我们可以使用@IntegrationTest注释测试,并且JUnit 5将使用@IntegrationTest中定义的两个扩展来运行测试:
@IntegrationTest
public class DemoTest {
...
}
尽管@ExtendWith易于使用并且在大多数情况下都能正常工作,但它有一个缺点。 有时测试代码需要与扩展交互,或者扩展可能需要某种配置或设置代码。 如果扩展名是使用@ExtendWith定义的,则无法完成此操作。
在这种情况下,我们可以手动创建扩展名,将其分配给一个字段并添加@RegisterExtension批注。 例如,让我们看一下一个虚构的扩展名,它可以在测试中管理临时文件:
public class DemoTest {
@RegisterExtension
static TempFileExtension tempFiles = TempFileExtension.builder()
.setDirectory( "/tmp" )
.deleteAfterTestExecution( true )
.build();
@Test
public void test() {
File f = tempFiles.newTempFile( "foobar.tmp" );
...
}
}
在字段上使用@RegisterExtension可让我们选择在测试方法中配置扩展并与扩展交互。
创建自定义扩展
为JUnit 5创建自定义扩展非常容易。 我们只需要创建一个实现一个或多个JUnits扩展接口的类即可。
假设我们要创建一个简单的扩展来衡量测试运行的时间。 为此,我们创建一个实现接口InvocationInterceptor的新类。
public class TestDurationReportExtension implements InvocationInterceptor {
@Override
public void interceptTestMethod(Invocation<Void> invocation,
ReflectiveInvocationContext<Method> invocationContext,
ExtensionContext extensionContext) throws Throwable {
long beforeTest = System.currentTimeMillis();
try {
invocation.proceed();
} finally {
long afterTest = System.currentTimeMillis();
long duration = afterTest - beforeTest;
String testClassName = invocationContext.getTargetClass().getSimpleName();
String testMethodName = invocationContext.getExecutable().getName();
System.out.println(String.format( "%s.%s: %dms" , testClassName, testMethodName, duration));
}
}
}
InvocationInterceptor具有各种使用默认实现的方法。 我们重写了interceptTestMethod(..)的实现。 此方法使我们可以在执行测试方法之前和之后运行代码。 使用Invocation方法参数的proceed()方法,我们可以继续实际的测试执行。
我们只需从测试执行后的系统时间中减去测试前的系统时间即可获得持续时间。 之后,我们使用InvocationContext参数获取测试类和测试方法的名称。 利用此信息,我们创建了格式化的输出消息。
现在,我们可以使用@ExtendWith批注使用TestDurationReportExtension扩展测试:
@ExtendWith (TestDurationReportExtension. class )
public class DemoTest { .. }
运行测试时,我们现在将看到每种测试方法的扩展输出。
使用两种方法进行测试的输出可能如下所示:
DemoTest.slowTest: 64ms
DemoTest.fastTest: 6ms
扩展接口
InvocationInterceptor只是各种扩展接口之一。 在本节中,我们将简要介绍这些不同的接口以及它们可以使用的接口。
有条件的测试执行
通过实现ExecutionCondition接口,扩展程序可以决定是否应该执行测试。 这使扩展程序可以决定是否应跳过某些测试。 一个简单的例子是标准的扩展DisabledCondition一个跳过与@Disabled注释测试。
测试实例工厂
默认情况下,JUnit 5将通过调用可用的构造函数来实例化测试类(如果有多个测试构造函数可用,则将引发异常)。 可能的构造函数参数使用ParameterResolver扩展解析(请参见下文)。
可以使用TestInstanceFactory接口自定义此默认行为。 实现TestInstanceFactory的扩展用作创建测试类实例的工厂。 这可用于通过静态工厂方法创建测试,或将其他参数注入测试构造函数。
处理测试实例
创建测试实例后,可以使用TestInstancePostProcessor接口发布流程测试实例。 一个常见的扩展用例是将依赖项注入测试实例的字段中。 类似地,当测试完成并且不再需要该实例时,可以使用TestInstancePreDestroyCallback运行自定义清理逻辑。
测试参数分辨率
用@ Test,@ BeforeEach,@ BeforeAll等注释的测试类构造函数或方法可以包含参数。 这些参数在运行时由JUnit使用ParameterResolvers解析。 如果扩展要支持其他参数,则可以实现ParameterResolver。
测试生命周期回调和拦截
JUnit 5提供了一些测试生命周期回调接口,可以通过扩展实现这些接口:
- BeforeAllCallback,在测试类中的@BeforeAll方法之前运行
- BeforeEachCallback,在测试类中的@BeforeEach方法之前运行
- BeforeTestExecutionCallback,在测试方法之前运行
- AfterTestExecutionCallback,在测试方法之后运行
- AfterEachCallback,在测试类中的@AfterEach方法之后运行
- AfterAllCallback,在测试类中的@AfterAll方法之后运行
这些接口提供了一个简单的回调,可以在测试生命周期中的特定时间执行某些操作。
另外,上面的扩展示例中已经使用了InvocationInterceptor接口。 InvocationInterceptor具有与回调接口类似的方法。 但是,InvocationInterceptor为我们提供了一个Invocation参数,该参数允许我们通过调用progress()方法来手动继续生命周期。 如果我们想将代码包装在调用周围,例如try / catch块,这将很有用。
概要
为JUnit 5编写扩展非常容易。 我们只需要创建一个实现一个或多个JUnits扩展接口的类即可。 可以使用@ExtendWith和@RegisterExtension批注将扩展添加到测试类(或方法)。 您可以在GitHub上找到示例扩展的源代码。 另外,还要确保查阅出色的JUnit 5用户指南。
翻译自: https://www.javacodegeeks.com/2020/08/extending-junit-5.html
junit5