JUnit 5 在测试中排除标记的测试方法 class

JUnit 5 exlude tagged test methods within test class

@SpringBootTest
@AutoConfigureMockMvc
@ExcludeTags({"no"})
public class MyClassTest {
   @Test
   public void test1() {
   }

   @Test
   @Tag("no")
   public void test2() {
   }
   ...
}

@RunWith(JUnitPlatform.class)
@SelectClasses({MyClassTest.class})
@IncludeTags({"no"})
public class MyClassTestSuiteTest {
}

有一个 Spring Boot 2.3.1 项目并测试一些 REST 控制器,在测试中 class 一些测试方法被标记,不应 运行,当 MyClassTest 是 运行。带注释的方法在测试套件中为 运行(带有 @IncludeTags("no")JUnit 5.6.2

对于测试套件,我不确定它 @RunWith 必须用于测试套件,还是 JUnit 5 @ExtendWith 是正确的?事实上,如果没有必要,我不想混用 JUnit 4 和 5,坚持使用 JUnit 5。

有没有一种方法可以简单地通过注释或类似方式进行配置,以便在 MyClassTest 为 运行 时不 运行 标记的方法?就像 @ExcludeTags 用于测试套件,但这不适用于示例中的 class。

也许可以创建两个测试套件,一个 @ExludeTags("no"),一个 @IncludeTags("no")。但是,如何完全防止 MyClassTest 它 运行?

我不想在特定 IDE 中创建一些 运行 配置。首选方法是使用注释或类似方法。也许 Maven 配置也足够了。

也许可以通过某些标准评估避免在测试方法级别执行特定测试方法,如果执行的测试 class 是 MyClassTest,则不要 运行 该测试方法。

有趣的是,我不能简单地将 @RunWith(JUnitPlatform.class) 替换为 @ExtendWith(JUnitPlatform.class),因为类型不兼容。使用 @ExtendWith(SpringExtension.class) 不会给我 运行 和 class 的可能性(例如,右键单击 class 名称,无法进入 Run/Debug) .但是 @ExtendWith 取代了 JUnit 5 中的 @RunWith,对 运行 测试套件使用什么扩展?

创建执行条件ExcludeTagsCondition

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.commons.util.AnnotationUtils;

public class ExcludeTagsCondition implements ExecutionCondition {

    private static final ConditionEvaluationResult ENABLED_IF_EXCLUDE_TAG_IS_INVALID =
            ConditionEvaluationResult.enabled(
                    "@ExcludeTags does not have a valid tag to exclude, all tests will be run");
    private static Set<String> tagsThatMustBeIncluded = new HashSet<>();

    public static void setMustIncludeTags(final Set<String> tagsThatMustBeIncluded) {
        ExcludeTagsCondition.tagsThatMustBeIncluded = new HashSet<>(tagsThatMustBeIncluded);
    }

    @Override
    public ConditionEvaluationResult evaluateExecutionCondition(
            ExtensionContext context) {
        final AnnotatedElement element = context
                .getElement()
                .orElseThrow(IllegalStateException::new);
        final Optional<Set<String>> tagsToExclude = AnnotationUtils.findAnnotation(
                context.getRequiredTestClass(),
                ExcludeTags.class
        )
        .map(a -> 
            Arrays.asList(a.value())
                    .stream()
                    .filter(t -> !tagsThatMustBeIncluded.contains(t))
                    .collect(Collectors.toSet())
        );
        if (!tagsToExclude.isPresent() || tagsToExclude.get().stream()
                .allMatch(s -> (s == null) || s.trim().isEmpty())) {
            return ENABLED_IF_EXCLUDE_TAG_IS_INVALID;
        }
        final Optional<String> tag = AnnotationUtils.findAnnotation(element, Tag.class)
                .map(Tag::value);
        if (tagsToExclude.get().contains(tag.map(String::trim).orElse(""))) {
            return ConditionEvaluationResult
                    .disabled(String.format(
                            "test method \"%s\" has tag \"%s\" which is on the @ExcludeTags list \"[%s]\", test will be skipped",
                            (element instanceof Method) ? ((Method) element).getName()
                                    : element.getClass().getSimpleName(),
                            tag.get(),
                            tagsToExclude.get().stream().collect(Collectors.joining(","))
                    ));
        }
        return ConditionEvaluationResult.enabled(
                String.format(
                        "test method \"%s\" has tag \"%s\" which is not on the @ExcludeTags list \"[%s]\", test will be run",
                        (element instanceof Method) ? ((Method) element).getName()
                                : element.getClass().getSimpleName(),
                        tag.orElse("<no tag present>"),
                        tagsToExclude.get().stream().collect(Collectors.joining(","))
                ));
    }
}

创建注释@ExcludeTags

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@ExtendWith(ExcludeTagsCondition.class)
public @interface ExcludeTags {
    String[] value();
}

考试中

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@ExcludeTags({"foo", "bar"})
@SpringBootTest
class AppTest {

    @Test
    @Tag("foo")
    void test1() {
        System.out.println("test1");
    }

    @Test
    @Tag("bar")
    void test2() {
        System.out.println("test2");
    }

    @Test
    @Tag("baz")
    void test3() {
        System.out.println("test3");
    }
}

当您 运行 测试时,您应该看到以下输出:

test method "test1" has tag "foo" which is on the @ExcludeTags list "[bar,foo]", test will be skipped

test method "test2" has tag "bar" which is on the @ExcludeTags list "[bar,foo]", test will be skipped

test3

你的测试 运行ner 应该显示 1 个测试通过和 2 个跳过。

现在开始测试套件:

创建注释@MustIncludeTags

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface MustIncludeTags {
    String[] value();
}

现在像这样设置你的测试套件:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
@SelectClasses({MyTestSuite.SetupTests.class, AppTest.class})
@MustIncludeTags({"foo", "bar"})
public class MyTestSuite {

    public static class SetupTests {
    
        @BeforeAll
        public static void beforeClass() {
            ExcludeTagsCondition.setMustIncludeTags(
                    Optional.ofNullable(MyTestSuite.class.getAnnotation(MustIncludeTags.class))
                            .map(MustIncludeTags::value)
                            .map(Arrays::asList)
                            .orElse(new ArrayList<>())
                            .stream()
                            .collect(Collectors.toSet())
            );
        }
    
        @Disabled
        @Test
        void testDummy() {
            // this test needs to be present for the beforeAll to run
        }
    
    }
}

当您 运行 带有 @MustIncludeTags 的测试套件时,@ExcludedTags 被覆盖。

从下面的测试执行可以看出: