Как запустить метод очистки только после теговых тестов? - PullRequest
0 голосов
/ 06 сентября 2018

Я пишу тесты JUnit 5 для моего проекта Java.

У меня есть несколько методов испытаний, которые требуют много времени для очистки (после каждого из них). В идеале я хотел бы пометить их аннотацией и запустить метод очистки только для них.

Вот что я попробовал:

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 из тестовых методов.

Ответы [ 3 ]

0 голосов
/ 06 сентября 2018

Вы можете создать собственное расширение 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.Cleanup или ExtendWith(CleanupExtension.class). Цель первой аннотации - объединить конфигурацию с аннотацией, которая также применяет расширение. Затем после каждого метода тестирования расширение будет вызывать любые методы в иерархии классов, помеченные CleanupExtension.CleanMethod. И Cleanup, и CleanMethod имеют атрибут String. Этот атрибут является «именем», и только CleanMethod s, которые имеют «имя», совпадающее с тестом Cleanup, будут выполнены. Это позволяет связать определенные методы тестирования с конкретными методами очистки.


Для получения дополнительной информации о расширениях JUnit Jupiter см. §5 Руководства пользователя . Также для CleanupExtension.Cleanup я использую функцию мета-аннотации / составной аннотации, описанную в §3.1.1 .

Обратите внимание, что это сложнее, чем ответ , заданный @ Roman Konoval , но он может быть более удобным, если вам придется делать подобные вещи много раз. Однако, если вам нужно сделать это только для одного или двух тестовых занятий, я рекомендую ответ Романа.

0 голосов
/ 06 сентября 2018

Из документации:

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() {

}
0 голосов
/ 06 сентября 2018

Вы можете ввести test в тест и проверить, какими тегами помечен тест:

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
  }

}
...