【问题标题】:How can I run cleanup method only after tagged tests?如何仅在标记测试后运行清理方法?
【发布时间】:2019-02-11 03:37:36
【问题描述】:

我正在为我的 Java 项目编写 JUnit 5 测试。

我有一些需要耗时清理的测试方法(在每个方法之后)。理想情况下,我想用一些注释标记它们并仅为它们运行清理方法。

这是我尝试过的:

class MyTest {

    @AfterEach
    @Tag("needs-cleanup")
    void cleanup() {
        //do some complex stuff
    }

    @Test
    void test1() {
         //do test1
    }

    @Test
    @Tag("needs-cleanup")
    void test2() {
         //do test2
    }
}

我希望 cleanup 方法仅在 test2 之后运行。但它实际上是在两次测试之后运行的。

是否可以通过 JUnit 5 注释的某种组合来实现它?我不想将我的测试类分成几个类或直接从测试方法调用cleanup

【问题讨论】:

  • 应该在每个特殊测试之后还是在所有特殊测试之后进行清理?
  • @Slaw 每次特殊测试后

标签: java junit junit5


【解决方案1】:

您可以将TestInfo 注入到测试中,并检查测试用什么标签进行注释:

class MyTest {
  private TestInfo testInfo;

  MyTest(TestInfo testInfo) {
    this.testInfo = testInfo;
  }

  @AfterEach
  void cleanup() {
    if (this.testInfo.getTags().contains("needs-cleanup")) {
        // .. do cleanup
    } 
  }

  @Test
  void test1() {
     //do test1
  }

  @Test
  @Tag("needs-cleanup")
  void test2() {
     //do test2
  }

}

【讨论】:

    【解决方案2】:

    您可以创建自己的AfterEachCallback 扩展并将其应用于所需的测试方法。此扩展将在应用它的每个测试后执行。然后,使用自定义注释,您可以将特定的清理方法与特定的测试联系起来。这是扩展的示例:

    import static java.lang.annotation.ElementType.METHOD;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import java.lang.reflect.Method;
    import java.util.List;
    import org.junit.jupiter.api.extension.AfterEachCallback;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.junit.jupiter.api.extension.ExtensionContext;
    import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
    import org.junit.jupiter.api.extension.ExtensionContext.Store;
    import org.junit.platform.commons.support.AnnotationSupport;
    import org.junit.platform.commons.support.HierarchyTraversalMode;
    
    public class CleanupExtension implements AfterEachCallback {
    
      private static final Namespace NAMESPACE = Namespace.create(CleanupExtension.class);
    
      private static boolean namesMatch(Method method, String name) {
        return method.getAnnotation(CleanMethod.class).value().equals(name);
      }
    
      private static Exception suppressOrReturn(final Exception previouslyThrown,
                                                final Exception newlyThrown) {
        if (previouslyThrown == null) {
          return newlyThrown;
        }
        previouslyThrown.addSuppressed(newlyThrown);
        return previouslyThrown;
      }
    
      @Override
      public void afterEach(final ExtensionContext context) throws Exception {
        final Method testMethod = context.getRequiredTestMethod();
        final Cleanup cleanupAnno = testMethod.getAnnotation(Cleanup.class);
        final String cleanupName = cleanupAnno == null ? "" : cleanupAnno.value();
    
        final List<Method> cleanMethods = getAnnotatedMethods(context);
    
        final Object testInstance = context.getRequiredTestInstance();
        Exception exception = null;
    
        for (final Method method : cleanMethods) {
          if (namesMatch(method, cleanupName)) {
            try {
              method.invoke(testInstance);
            } catch (Exception ex) {
              exception = suppressOrReturn(exception, ex);
            }
          }
        }
    
        if (exception != null) {
          throw exception;
        }
      }
    
      @SuppressWarnings("unchecked")
      private List<Method> getAnnotatedMethods(final ExtensionContext methodContext) {
        // Use parent (Class) context so methods are cached between tests if needed
        final Store store = methodContext.getParent().orElseThrow().getStore(NAMESPACE);
        return store.getOrComputeIfAbsent(
            methodContext.getRequiredTestClass(),
            this::findAnnotatedMethods,
            List.class
        );
      }
    
      private List<Method> findAnnotatedMethods(final Class<?> testClass) {
        final List<Method> cleanMethods = AnnotationSupport.findAnnotatedMethods(testClass,
            CleanMethod.class, HierarchyTraversalMode.TOP_DOWN);
    
    
        for (final Method method : cleanMethods) {
          if (method.getParameterCount() != 0) {
            throw new IllegalStateException("Methods annotated with "
                + CleanMethod.class.getName() + " must not have parameters: "
                + method
            );
          }
        }
    
        return cleanMethods;
      }
    
      @ExtendWith(CleanupExtension.class)
      @Retention(RUNTIME)
      @Target(METHOD)
      public @interface Cleanup {
    
        String value() default "";
    
      }
    
      @Retention(RUNTIME)
      @Target(METHOD)
      public @interface CleanMethod {
    
        String value() default "";
    
      }
    
    }
    

    然后您的测试类可能如下所示:

    import org.junit.jupiter.api.Test;
    
    class Tests {
    
      @Test
      @CleanupExtension.Cleanup
      void testWithExtension() {
        System.out.println("#testWithExtension()");
      }
    
      @Test
      void testWithoutExtension() {
        System.out.println("#testWithoutExtension()");
      }
    
      @Test
      @CleanupExtension.Cleanup("alternate")
      void testWithExtension_2() {
        System.out.println("#testWithExtension_2()");
      }
    
      @CleanupExtension.CleanMethod
      void performCleanup() {
        System.out.println("#performCleanup()");
      }
    
      @CleanupExtension.CleanMethod("alternate")
      void performCleanup_2() {
        System.out.println("#performCleanup_2()");
      }
    
    }
    

    运行Tests 我得到以下输出:

    #testWithExtension()
    #performCleanup()
    #testWithExtension_2()
    #performCleanup_2()
    #testWithoutExtension()
    

    此扩展将应用于任何带有CleanupExtension.CleanupExtendWith(CleanupExtension.class) 注释的测试方法。前一个注解的目的是将配置与同样应用扩展的注解结合起来。然后,在每个测试方法之后,扩展将调用类层次结构中使用CleanupExtension.CleanMethod 注释的任何方法。 CleanupCleanMethod 都具有 String 属性。该属性是“名称”,只有与Cleanup 测试具有匹配“名称”的CleanMethods 才会被执行。这允许您将特定的测试方法链接到特定的清理方法。


    有关 JUnit Jupiter 扩展的更多信息,请参阅§5 of the User Guide。另外,对于CleanupExtension.Cleanup,我正在使用§3.1.1 中描述的元注释/组合注释功能。

    请注意,这比 @Roman Konoval 给出的the answer 更复杂,但如果您必须多次执行此类操作,它可能对用户更友好。但是,如果您只需要为一两个测试课程执行此操作,我推荐 Roman 的答案。

    【讨论】:

      【解决方案3】:

      来自文档:

      TestInfo:如果方法参数是 TestInfo 类型,则 TestInfoParameterResolver 将提供一个 TestInfo 实例 对应于当前测试作为参数的值。这 然后可以使用 TestInfo 检索有关当前 测试,例如测试的显示名称、测试类、测试方法、 或相关标签。显示名称是技术名称,例如 作为测试类或测试方法的名称,或自定义名称 通过@DisplayName 配置。

      TestInfo 充当 TestName 规则的替代品 JUnit 4.

      关于上述描述,您可以使用 TestInfo 类,它会为您提供 cleanUp 应该运行的类的信息,然后您需要检查条件并通过检查标签来允许您想要的类:

      @AfterEach 
      void afterEach(TestInfo info) {
          if(!info.getTags().contains("cleanItUp")) return; // preconditioning only to needs clean up
              //// Clean up logic Here
      }
      
      
      @Test
      @Tag("cleanItUp")
      void myTest() {
      
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-10
        • 1970-01-01
        • 2012-11-09
        • 2017-06-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多