如何立即重新运行失败的JUnittesting?
有没有办法让JUnit规则或类似的东西,让每一个失败的testing第二次机会,只是试图再次运行它。
背景:我有一个使用JUnit编写的一套Selenium2-WebDrivertesting。 由于时间非常激烈(点击之后只有很短的等待时间),一些testing(100个中的1个,总是一个不同的testing)可能会失败,因为服务器有时反应比较慢。 但是我不能等待很长的时间,因为这样testing就会一直持续下去。) – 所以我认为这个用例是可以接受的,即使testing是绿色的,即使需要第二个testing尝试。
当然,最好有三分之二的多数(重复三次失败的testing,如果其中两次testing是正确的,就把它们作为正确的),但是这是未来的改进。
你可以用TestRule来做到这一点 。 这会给你你需要的灵活性。 TestRule允许你在testing周围插入逻辑,所以你可以实现重试循环:
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()
,它调用你的testing方法。 所以围绕这个调用你把一个重试循环。 如果在你的testing方法中抛出exception(断言失败实际上是一个AssertionError
),那么testing失败了,你会重试。
还有一件可能有用的东西。 您可能只想将此重试逻辑应用于一组testing,在这种情况下,您可以将其添加到testing上方的Retry类中,以用于该方法上的特定注释。 Description
包含该方法的注释列表。 有关这方面的更多信息,请参阅我的答案如何在每个JUnit @Test方法之前单独运行一些代码,而不使用@RunWith和AOP? 。
使用自定义TestRunner
这是CKuck的build议,你可以定义你自己的亚军。 您需要扩展BlockJUnit4ClassRunner并覆盖runChild()。 有关更多信息,请参阅我的答案如何在套件中定义JUnit方法规则? 。 这个答案详细说明了如何定义如何在Suite中为每个方法运行代码,为此您必须定义自己的Runner。
至于我写定制亚军更灵活的解决scheme。 上面发布的解决scheme(使用代码示例)有两个缺点:
- 如果在@BeforeClass阶段失败,它将不会重试testing;
- 它计算testing运行有点不同(当你有3次重试,你会得到testing运行:4,成功1可能会混淆);
这就是为什么我更喜欢编写自定义亚军的方法。 而自定义亚军的代码可以如下:
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); } }
现在有一个更好的select。 如果您使用的是maven插件,如surfire或failsefe,则可以select添加参数rerunFailingTestsCount
SurFire Api 。 这个东西在下面的票中实现: Jira Ticket 。 在这种情况下,您不需要编写自定义代码,插件会自动修改testing结果报告。
我看到这种方法只有一个缺点:如果一些testing失败上课/之后阶段testing将不会重新运行。
你必须编写你自己的org.junit.runner.Runner
并用@RunWith(YourRunner.class)
注释你的testing。