如何在Junit5中控制测试执行结果?

时间:2018-12-17 13:31:53

标签: java junit junit5

我在JUnit5中创建了一个新注释,该注释创建了相同测试的复制流,并根据某些条件运行它们或将其禁用。

但是,如果至少有一次迭代失败,它将自动使整个测试套件失败,并且我希望能够控制父级测试的执行结果。

例如,我要设置为如果经过一定数量的副本,则整个套件都应该通过。 有什么办法吗?

这是我的代码:

public class Test {

private static int i = 0;

    @FlakyTest(maxIterations = 10, maxFailuresRate = 0.4)
    public void test() {
        if(i++ == 0){
            assert false;
        } else {
            assert true;
        }
    }
}


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

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@TestTemplate
@ExtendWith(FlakyTestRunner.class)
public @interface FlakyTest {

    int maxIterations() default 6;
    double maxFailuresRate() default 0.2;
}


import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static flaky.FlakyTestRunner.didPassedFailureRate;
import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated;

public class FlakyTestRunner implements TestTemplateInvocationContextProvider, AfterTestExecutionCallback {

    public static int iteration = 0;
    public static int maxIterations;
    public static double maxFailuresRate;
    private static Map<Integer, Boolean> iterationsResultsMap = new HashMap<>();

    @Override
    public boolean supportsTestTemplate(ExtensionContext extensionContext) {
        return isAnnotated(extensionContext.getTestMethod(), FlakyTest.class);
    }

    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext extensionContext) {
        maxIterations = extensionContext.getElement().get().getAnnotation(FlakyTest.class).maxIterations();
        maxFailuresRate = extensionContext.getElement().get().getAnnotation(FlakyTest.class).maxFailuresRate();
        List invocationContexts = new ArrayList<TestTemplateInvocationContext>();
        for (int i = 0; i < maxIterations; i++) {
            invocationContexts.add(new FlakyIterationRunnerTemplateInvocationContext());
        }
        return invocationContexts.stream();
    }

    @Override
    public void afterTestExecution(ExtensionContext extensionContext) {
        iterationsResultsMap.put(iteration, !extensionContext.getExecutionException().isPresent());
    }

    public static boolean didPassedFailureRate() {
        if (iteration > 2) {
            return getFailedTestsRate() >= maxFailuresRate;
        }
        return false;
    }

    private static double getFailedTestsRate() {
        int sum = iterationsResultsMap.values()
                                      .stream()
                                      .mapToInt(successFlag -> successFlag ? 0 : 1)
                                      .sum();
        return ((double) sum) / maxIterations;
    }
}

class FlakyIterationRunnerTemplateInvocationContext implements TestTemplateInvocationContext {

    @Override
    public List<Extension> getAdditionalExtensions() {
        List<Extension> extensions = new ArrayList<>();
        extensions.add(new FlakyIterationRunnerExecutionCondition());
        return extensions;
    }
}

class FlakyIterationRunnerExecutionCondition implements ExecutionCondition {

    @Override
    public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext extensionContext) {
        FlakyTestRunner.iteration++;
        if (FlakyTestRunner.iteration <= FlakyTestRunner.maxIterations && !didPassedFailureRate()) {
            return ConditionEvaluationResult.enabled("Passed");
        }
        return ConditionEvaluationResult.disabled("Iteration number: " + FlakyTestRunner.iteration + ", did passed failure rate? " + didPassedFailureRate()
                + ". Max failures rate allowed - " + FlakyTestRunner.maxFailuresRate);
    }
}

1 个答案:

答案 0 :(得分:0)

我一般认为“ flask测试”是一个坏主意,几乎可以总是进行某种程度的重构以消除对此类事情的需要。但是,如果您要采用这种方法,我想您可以通过使扩展实现TestExecutionExceptionHandler来实现它,以便您可以确定AssertionError何时应导致测试失败。我不知道这是否是一个特别好的实现,但是我想这会满足您的要求:

public class FlakyTestRunner implements TestTemplateInvocationContextProvider, TestExecutionExceptionHandler  {

    private Map<Method,MultiIterationResult> results = new HashMap<>();

    @Override
    public boolean supportsTestTemplate(ExtensionContext extensionContext) {
        return isAnnotated(extensionContext.getTestMethod(), FlakyTest.class);
    }

    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
        ExtensionContext extensionContext) {
        int maxIterations = extensionContext.getElement().get().getAnnotation(FlakyTest.class).maxIterations();
        double maxFailuresRate = extensionContext.getElement().get().getAnnotation(FlakyTest.class).maxFailuresRate();
        results.put(extensionContext.getTestMethod().get(), new MultiIterationResult(maxIterations, maxFailuresRate));
        List invocationContexts = new ArrayList<TestTemplateInvocationContext>();
        for (int i = 0; i < maxIterations; i++) {
            invocationContexts.add(new FlakyIterationRunnerTemplateInvocationContext());
        }
        return invocationContexts.stream();
    }

    @Override
    public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
        results.get(context.getTestMethod().orElseThrow(() -> throwable)).handleFailure(throwable);
    }

    private class MultiIterationResult {
        private final int iterations;
        private final double failureThreshold;
        private int failCount = 0;

        public MultiIterationResult(int iterations, double failureThreshold) {
            this.iterations = iterations;
            this.failureThreshold = failureThreshold;
        }

        public void handleFailure(Throwable throwable) throws Throwable {
            failCount++;
            if((double)failCount/iterations > failureThreshold) {
                throw throwable;
            }
        }
    }

    private  class FlakyIterationRunnerTemplateInvocationContext implements TestTemplateInvocationContext {
    }
}