如何测试扩展实施 [英] How to test extension implementations
问题描述
JUnit 5 API中有几个扩展点.
There are several extension points available in the JUnit 5 API.
例如:
-
AfterEachCallback
a> -
AfterAllCallback
-
AfterTestExecutionCallback
-
BeforeAllCallback
-
BeforeEachCallback
-
BeforeTestExecutionCallback
-
ExecutionCondition
-
ParameterResolver
-
TestExecutionExceptionHandler
-
TestInstancePostProcessor
-
TestTemplateInvocationContextProvider
AfterEachCallback
AfterAllCallback
AfterTestExecutionCallback
BeforeAllCallback
BeforeEachCallback
BeforeTestExecutionCallback
ExecutionCondition
ParameterResolver
TestExecutionExceptionHandler
TestInstancePostProcessor
TestTemplateInvocationContextProvider
几乎所有这些扩展都采用测试类,测试实例,具有存储值和其他功能.其中许多直接在类实例上运行
Nearly all of these extensions take some form of ExtensionContext
which provides access to the test class, test method, test instance, ability to publish report entries, store values, and other capabilities. A lot of these operate directly on class instances
Most implementations will probably use some combination of reflection using ReflectionSupport
and annotation discovery using AnnotationSupport
, which are all static methods, which makes them difficult to mock.
现在,假设我已经编写了ExecutionCondition
的实现,或者同时实现了BeforeEachCallback
和AfterEachCallback
的扩展-我如何有效地测试它们?
Now, say I have written an implementation of the ExecutionCondition
, or an extension that implements both the BeforeEachCallback
and AfterEachCallback
- how do I effectively test them?
我想我可以模拟ExtensionContext
,但这似乎不是有效地练习不同代码路径的好方法.
I guess I could mock ExtensionContext
, but that doesn't seem like a good way to effectively exercise the different code paths.
在JUnit内部,有一个
Internally to JUnit, there is the ExecutionEventRecorder
and it is used along with the JupiterTestEngine
implementation for execution (which is @API(status=INTERNAL)
) to select and execute the tests, and then perform assertions on the resulting events.
由于JupiterTestEngine
上的public
可见性,我可以使用相同的模式.
I could make use of the same pattern because of the public
visibility on the JupiterTestEngine
.
以下是一些具体的例子来说明:
Here are a few concrete examples to demonstrate:
-
一个
ExecutionCondition
,它可以基于系统属性进行测试.可以进行一些反思并使用TestInfo
为@BeforeEach
或@AfterEach
样式,但是处理并行测试似乎很复杂,并且可能会出现问题.此示例显示了如何提供真实的ExtensionContext
并根据结果进行断言"的示例.
An
ExecutionCondition
that enables tests based on system properties. This can possibly be tested with some reflection and usingTestInfo
in a@BeforeEach
or@AfterEach
style, but it seems more complicated to deal with and possible issues when parallel test execution comes. This example shows an example of "how do I provide a real-lifeExtensionContext
and assert on the result".
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SystemPropertyCondition.class)
public @interface SystemProperty {
String key();
String value();
}
public class SystemPropertyCondition implements ExecutionCondition {
@Override
public ConditionEvaluationResult evaluateExecutionCondition(final ExtensionContext context) {
return context.getTestMethod()
.flatMap(element -> AnnotationSupport.findAnnotation(element, SystemProperty.class))
.map(systemProperty -> {
if (systemProperty.value().equals(System.getProperty(systemProperty.key()))) {
return ConditionEvaluationResult.enabled("Property is available");
} else {
return ConditionEvaluationResult.disabled("Property not equal");
}
})
.orElseGet(() -> ConditionEvaluationResult.enabled("No annotations"));
}
}
仅在指定星期几运行的ExecutionCondition
.测试时钟将如何注入?您将如何测试条件是否为预期结果?我猜一些状态/实现可以放在 ExtensionContext.Store
,这将允许扩展之间传递某种状态(例如,使用BeforeEachCallback
也可能是正确的路径,但这将取决于顺序我不相信BeforeEachCallback
在ExecutionCondition
之前被调用,因此该路径可能不是正确的路径.此示例既显示了我如何注入依赖项",也显示了与提供ExtensionContext
并声明结果的上一示例.
An ExecutionCondition
that only runs on the provided day of the week. How would a test clock be injected? How would you test if the condition was the expected result? I guess some state/implementations can be placed into a ExtensionContext.Store
which would allow for some sort of state being passed between extensions (like with a BeforeEachCallback
as well, which might be the right path, but that is going to depend on the ordering of extension execution. I don't believe BeforeEachCallback
is called before an ExecutionCondition
, so that path might not be the right one. This example shows both the "how do I inject dependencies" and also shows the same issue as the previous example of providing the ExtensionContext
and asserting on the result.
@ExtendWith(RunOnDayCondition.class)
public @interface RunOnDay {
DayOfWeek[] value();
}
final class RunOnDayCondition implements ExecutionCondition {
private static final ConditionEvaluationResult DEFAULT = disabled(
RunOnDay.class + " is not present"
);
@Override
public ConditionEvaluationResult evaluateExecutionCondition(final ExtensionContext context) {
return context.getElement()
.flatMap(annotatedElement -> findAnnotation(annotatedElement, RunOnDay.class))
.map(RunOnDay::value)
.map(RunOnDayCondition::evaluateIfRunningOnDay)
.orElse(DEFAULT);
}
private static ConditionEvaluationResult evaluateIfRunningOnDay(final DayOfWeek[] days) {
// TODO: How would you inject a test clock?
final DayOfWeek currentDay = LocalDate.now().getDayOfWeek();
final boolean runningInday = Stream.of(days).anyMatch(currentDay::equals);
if (runningInday) {
return enabled("Current day is " + currentDay + ", in the specified days of " + Arrays.toString(days));
} else {
return disabled("Current day is " + currentDay + ", not in the specified days of " + Arrays.toString(days));
}
}
}
一个扩展程序,它设置一个临时目录,将其提供为参数,然后在测试后将其清除.这将使用ParameterResolver
,BeforeEachCallback
,AfterEachCallback
和ExtensionContext.Store
.此示例说明扩展实现可以使用多个扩展点,并且可以利用存储来跟踪状态.
An extension that sets up a temporary directory, provides it as a parameter, and then cleans it up after the test. This would use ParameterResolver
, BeforeEachCallback
, AfterEachCallback
, and the ExtensionContext.Store
. This example shows that an extension implementation may use multiple extensions points and can make use of the store to keep track of state.
针对扩展测试的自定义测试引擎会是正确的方法吗?
Would a custom test engine for extension tests be the right approach?
那么,如何在不依赖内部API的情况下以及在模拟过程中不进行重复"工作的情况下测试各种扩展实现?
So, how do I test various extension implementations without relying on internal APIs and without "duplicating" effort into mocks?
推荐答案
我知道这不是一个完整的答案(但是我现在在移动设备上,您的问题很复杂)...但是,如果 此项目可以为您提供帮助.
I know that is not a complete answer (but I am on mobile now and your question is very complex)... Try however if this project could help you.
如果您的问题有所限制,我会尽力帮助您
If you limit a little your question I could try to help you better
这篇关于如何测试扩展实施的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!