如何立即重新运行失败的JUnit测试?

kqlmhetl  于 2022-11-11  发布在  其他
关注(0)|答案(7)|浏览(248)

有没有一种方法可以让JUnit规则或类似的东西给每个失败的测试第二次机会,只是尝试再次运行它。

  • 背景:我有一个用JUnit编写的大量Selenium 2-WebDriver测试集。(仅在点击后短暂等待)一些测试(100分之一,而且总是不同的)可能会失败,因为服务器有时响应有点慢。但我不能让等待时间这么长,它肯定足够长,因为那样的话,测试将永远持续下去。)--所以我认为对于这个用例来说,测试是绿色的是可以接受的,即使它需要第二次尝试。*

当然,最好是三分之二的多数(重复三次失败的测试,如果其中两次测试正确,就认为它们是正确的),但这将是未来的改进。

y1aodyip

y1aodyip1#

你可以用TestRule来实现这个功能,这将给予你所需要的灵活性。TestRule允许你在测试中插入逻辑,这样你就可以实现retry循环:

public class RetryTest {
    public class Retry implements TestRule {
        private int retryCount;

        public Retry(int retryCount) {
            this.retryCount = retryCount;
        }

        public Statement apply(Statement base, Description description) {
            return statement(base, description);
        }

        private Statement statement(final Statement base, final Description description) {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    Throwable caughtThrowable = null;

                    // implement retry logic here
                    for (int i = 0; i < retryCount; i++) {
                        try {
                            base.evaluate();
                            return;
                        } catch (Throwable t) {
                            caughtThrowable = t;
                            System.err.println(description.getDisplayName() + ": run " + (i+1) + " failed");
                        }
                    }
                    System.err.println(description.getDisplayName() + ": giving up after " + retryCount + " failures");
                    throw caughtThrowable;
                }
            };
        }
    }

    @Rule
    public Retry retry = new Retry(3);

    @Test
    public void test1() {
    }

    @Test
    public void test2() {
        Object o = null;
        o.equals("foo");
    }
}

TestRule的核心是base.evaluate(),它调用你的测试方法。因此,在这个调用周围放一个retry循环。如果在你的测试方法中抛出了一个异常(Assert失败实际上是一个AssertionError),那么测试失败了,你将重试。
还有一个可能有用的东西。您可能只想将这个重试逻辑应用到一组测试中,在这种情况下,您可以在Retry类中添加一个测试,用于方法上的特定注解。Description包含一个方法注解列表。有关此内容的更多信息,请参见我对如何在每个JUnit @Test方法之前单独运行一些代码的回答。而不使用@RunWith或AOP?。

使用自定义TestRunner

这是CKuck的建议,你可以定义你自己的Runner。你需要扩展BlockJUnit4ClassRunner并覆盖runChild()。更多信息请参见我对如何在套件中定义JUnit方法规则的回答。这个回答详细说明了如何为套件中的每个方法定义如何运行代码,为此你必须定义自己的Runner。

eqoofvh9

eqoofvh92#

现在有一个更好的选择。如果你使用的是maven插件,比如:surfire或failefe有一个添加参数rerunFailingTestsCountSurFire Api的选项。此功能在以下票证中实现:Jira Ticket。在这种情况下,你不需要写你的自定义代码和插件自动修改测试结果报告。
我认为这种方法只有一个缺点:如果某个测试在课前/课后阶段失败,则不会重新运行测试。

velaa5lx

velaa5lx3#

至于我写自定义运行更灵活的解决方案。解决方案,张贴以上(与代码示例)有两个缺点:
1.如果在@BeforeClass阶段失败,它将不会重试测试;
1.它计算测试运行有点不同(当您有3次重试时,您将收到测试运行:4、成功1可能令人困惑);
这就是为什么我更喜欢编写自定义runner的方法。自定义runner的代码可以如下:

import org.junit.Ignore;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runner.notification.StoppedByUserException;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;

public class RetryRunner extends BlockJUnit4ClassRunner {

    private final int retryCount = 100;
    private int failedAttempts = 0;

    public RetryRunner(Class<?> klass) throws InitializationError {
        super(klass);
    }    

    @Override
    public void run(final RunNotifier notifier) {
        EachTestNotifier testNotifier = new EachTestNotifier(notifier,
                getDescription());
        Statement statement = classBlock(notifier);
        try {

            statement.evaluate();
        } catch (AssumptionViolatedException e) {
            testNotifier.fireTestIgnored();
        } catch (StoppedByUserException e) {
            throw e;
        } catch (Throwable e) {
            retry(testNotifier, statement, e);
        }
    }

    @Override
    protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
        Description description = describeChild(method);
        if (method.getAnnotation(Ignore.class) != null) {
            notifier.fireTestIgnored(description);
        } else {
            runTestUnit(methodBlock(method), description, notifier);
        }
    }

    /**
     * Runs a {@link Statement} that represents a leaf (aka atomic) test.
     */
    protected final void runTestUnit(Statement statement, Description description,
            RunNotifier notifier) {
        EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
        eachNotifier.fireTestStarted();
        try {
            statement.evaluate();
        } catch (AssumptionViolatedException e) {
            eachNotifier.addFailedAssumption(e);
        } catch (Throwable e) {
            retry(eachNotifier, statement, e);
        } finally {
            eachNotifier.fireTestFinished();
        }
    }

    public void retry(EachTestNotifier notifier, Statement statement, Throwable currentThrowable) {
        Throwable caughtThrowable = currentThrowable;
        while (retryCount > failedAttempts) {
            try {
                statement.evaluate();
                return;
            } catch (Throwable t) {
                failedAttempts++;
                caughtThrowable = t;
            }
        }
        notifier.addFailure(caughtThrowable);
    }
}
rm5edbpk

rm5edbpk4#

建议的评论是根据ob this文章编写的,并添加了一些内容。
这里,如果你的jUnit项目中的某个测试用例得到“失败”或“错误”的结果,这个测试用例将被重新运行一次。这里我们总共设置了3次成功的机会。
因此,我们需要创建规则类将“@Rule”通知添加到您的测试类
如果您不想为每个测试类编写相同的“@Rule”通知,则可以将其添加到抽象SetProperty类(如果有)并从其扩展。

规则类别:

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class RetryRule implements TestRule {
    private int retryCount;

    public RetryRule (int retryCount) {
        this.retryCount = retryCount;
    }

    public Statement apply(Statement base, Description description) {
        return statement(base, description);
    }

    private Statement statement(final Statement base, final Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                Throwable caughtThrowable = null;

                // implement retry logic here
                for (int i = 0; i < retryCount; i++) {
                    try {
                        base.evaluate();
                        return;
                    } catch (Throwable t) {
                        caughtThrowable = t;
                        //  System.out.println(": run " + (i+1) + " failed");
                        System.err.println(description.getDisplayName() + ": run " + (i + 1) + " failed.");
                    }
                }
                System.err.println(description.getDisplayName() + ": giving up after " + retryCount + " failures.");
                throw caughtThrowable;
            }
        };
    }
}

测试类别:

import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

/**
 * Created by ONUR BASKIRT on 27.03.2016.
 */
public class RetryRuleTest {

    static WebDriver driver;
    final private String URL = "http://www.swtestacademy.com";

    @BeforeClass
    public static void setupTest(){
        driver = new FirefoxDriver();
    }

    //Add this notification to your Test Class 
    @Rule
    public RetryRule retryRule = new RetryRule(3);

    @Test
    public void getURLExample() {
        //Go to www.swtestacademy.com
        driver.get(URL);

        //Check title is correct
        assertThat(driver.getTitle(), is("WRONG TITLE"));
    }
}
k5hmc34c

k5hmc34c5#

您必须编写自己的org.junit.runner.Runner并使用@RunWith(YourRunner.class)注解您的测试。

wooyq4lh

wooyq4lh6#

至于Junit 5,Junit-pioneer扩展https://junit-pioneer.org/docs/retrying-test/提供了一个很酷的特性@RetryingTest,简单的例子:

public class RetryTest {
    private int counter = 0;

    @RetryingTest(5)
    void retryCounter() {
        if (counter++ < 2) fail();
    }

}

这将失败两次,并在第三次执行时显示绿色。

t5fffqht

t5fffqht7#

这个答案建立在this answer之上。
如果您需要在每次运行之前重新创建ActivityScenario(以及“活动”),您可以使用try-with-resources启动它。ActivityScenario在每次尝试后都会自动关闭。

public final class RetryRule<A extends Activity> implements TestRule {
    private final int retryCount;
    private final Class<A> activityClazz;
    private ActivityScenario<A> scenario;

    /**
     * @param retryCount the number of retries. retryCount = 1 means 1 (normal) try and then
     * 1 retry, i.e. 2 tries overall
     */
    public RetryRule(int retryCount, @NonNull Class<A> clazz) {
        this.retryCount = retryCount;
        this.activityClazz = clazz;
    }

    public Statement apply(Statement base, Description description) {
        return statement(base, description);
    }

    private Statement statement(final Statement base, final Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                Throwable caughtThrowable = null;

                // implement retry logic here
                for (int i = 0; i <= retryCount; i++) {
                    try(ActivityScenario<A> scenario = ActivityScenario.launch(activityClazz)){
                        RetryRule.this.scenario = scenario;
                        base.evaluate();
                        return;
                    } catch (Throwable t) {
                        caughtThrowable = t;
                        Log.e(LOGTAG,
                                description.getDisplayName() + ": run " + (i + 1) + " failed: ", t);
                    }
                }
                Log.e(LOGTAG,
                        description.getDisplayName() + ": giving up after " + (retryCount + 1) +
                                " failures");
                throw Objects.requireNonNull(caughtThrowable);
            }
        };
    }

    public ActivityScenario<A> getScenario() {
        return scenario;
    }
}

然后,您可以使用getScenario()方法访问测试中的场景。

相关问题