【发布时间】:2017-10-20 01:43:15
【问题描述】:
JUnit 5 API 中有几个可用的扩展点。
例如:
AfterEachCallbackAfterAllCallbackAfterTestExecutionCallbackBeforeAllCallbackBeforeEachCallbackBeforeTestExecutionCallbackExecutionConditionParameterResolverTestExecutionExceptionHandlerTestInstancePostProcessorTestTemplateInvocationContextProvider
几乎所有这些扩展都采用某种形式的ExtensionContext,它提供对test class、test method、test instance、publish report entries、store values 和其他功能的访问。其中很多直接在类实例上操作
大多数实现可能会使用reflection using ReflectionSupport 和annotation discovery using AnnotationSupport 的某种组合,它们都是静态方法,这使得它们难以模拟。
现在,假设我已经编写了 ExecutionCondition 的实现,或者实现了 BeforeEachCallback 和 AfterEachCallback 的扩展 - 我如何有效地测试它们?
我想我可以模拟 ExtensionContext,但这似乎不是有效地练习不同代码路径的好方法。
在 JUnit 内部,有 ExecutionEventRecorder,它与 JupiterTestEngine implementation for execution (which is @API(status=INTERNAL)) 一起用于选择和执行测试,然后是 perform assertions on the resulting events。
由于JupiterTestEngine 上的public 可见性,我可以使用相同的模式。
这里有几个具体的例子来演示:
-
一个
ExecutionCondition,它启用基于系统属性的测试。这可以通过一些反射和以@BeforeEach或@AfterEach样式使用TestInfo进行测试,但是当并行测试执行到来时,处理和可能的问题似乎更复杂。此示例显示了“如何提供真实的ExtensionContext并在结果上断言”的示例。@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并对结果进行断言的示例一样。@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。这个例子表明一个扩展实现可以使用多个扩展点,并且可以利用存储来跟踪状态。
用于扩展测试的自定义测试引擎是正确的方法吗?
那么,如何在不依赖内部 API 且不“复制”模拟工作的情况下测试各种扩展实现?
【问题讨论】:
标签: junit5