我在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);
}
}
答案 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 {
}
}