如何测试扩展实施 [英] How to test extension implementations

查看:62
本文介绍了如何测试扩展实施的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

JUnit 5 API中有几个扩展点.

There are several extension points available in the JUnit 5 API.

例如:

  • 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的实现,或者同时实现了BeforeEachCallbackAfterEachCallback的扩展-我如何有效地测试它们?

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:

  1. 一个ExecutionCondition,它可以基于系统属性进行测试.可以进行一些反思并使用 TestInfo @BeforeEach@AfterEach样式,但是处理并行测试似乎很复杂,并且可能会出现问题.此示例显示了如何提供真实的ExtensionContext并根据结果进行断言"的示例.

  1. An ExecutionCondition that enables tests based on system properties. This can possibly be tested with some reflection and using TestInfo 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-life ExtensionContext 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也可能是正确的路径,但这将取决于顺序我不相信BeforeEachCallbackExecutionCondition之前被调用,因此该路径可能不是正确的路径.此示例既显示了我如何注入依赖项",也显示了与提供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));
        }
      }
    }
    

  • 一个扩展程序,它设置一个临时目录,将其提供为参数,然后在测试后将其清除.这将使用ParameterResolverBeforeEachCallbackAfterEachCallbackExtensionContext.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屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆