Java 如何立即重新运行失败的JUnit测试?
有没有一种方法可以使用JUnit规则或类似的东西,让每一个失败的测试都有第二次机会,只需再次尝试运行它 背景:我用JUnit编写了大量Selenium2 WebDriver测试。由于非常激进的计时(单击后只有很短的等待时间),一些测试(100个测试中有1个,并且总是不同的测试)可能会失败,因为服务器有时响应较慢。但是我不能让等待时间太长以至于它肯定足够长,因为这样测试将永远持续下去。)--因此我认为对于这个用例来说,绿色测试是可以接受的,即使它需要第二次尝试Java 如何立即重新运行失败的JUnit测试?,java,testing,junit,Java,Testing,Junit,有没有一种方法可以使用JUnit规则或类似的东西,让每一个失败的测试都有第二次机会,只需再次尝试运行它 背景:我用JUnit编写了大量Selenium2 WebDriver测试。由于非常激进的计时(单击后只有很短的等待时间),一些测试(100个测试中有1个,并且总是不同的测试)可能会失败,因为服务器有时响应较慢。但是我不能让等待时间太长以至于它肯定足够长,因为这样测试将永远持续下去。)--因此我认为对于这个用例来说,绿色测试是可以接受的,即使它需要第二次尝试 当然,最好是三分之二的多数(重复一个
当然,最好是三分之二的多数(重复一个失败的测试三次,如果其中两个测试是正确的,就认为它们是正确的),但这将是一个未来的改进。您必须编写自己的
org.junit.runner.runner
,并用@RunWith(YourRunner.class)注释您的测试
您可以使用一个。这将为您提供所需的灵活性。TestRule允许您在测试周围插入逻辑,以便实现重试循环:
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");
}
}
公共类RetryTest{
公共类重试实现TestRule{
私人内部检索计数;
公共重试(int retryCount){
this.retryCount=retryCount;
}
公开声明应用(声明库、说明){
返回语句(基、描述);
}
专用报表(最终报表库、最终说明){
返回新语句(){
@凌驾
public void evaluate()可丢弃{
Throwable caughtThrowable=null;
//在这里实现重试逻辑
for(int i=0;i
TestRule
的核心是调用测试方法的base.evaluate()
。因此,围绕这个调用,您将设置一个重试循环。如果在测试方法中抛出异常(断言失败实际上是AssertionError
),则测试失败,您将重试
还有一件事可能有用。您可能只希望将此重试逻辑应用于一组测试,在这种情况下,您可以将方法上特定注释的测试添加到重试类之上<代码>说明包含方法的注释列表。有关这方面的更多信息,请参阅我对的回答
使用自定义TestRunner
这是CKuck的建议,您可以定义自己的跑步者。您需要扩展并重写runChild()。有关更多信息,请参阅我对的回答。这个答案详细说明了如何定义如何为套件中的每个方法运行代码,为此您必须定义自己的运行程序。至于我编写自定义运行程序更灵活的解决方案。 上面发布的解决方案(带有代码示例)有两个缺点:
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);
}
}
import org.junit.Ignore;
导入org.junit.internal.AssumptionViolatedException;
导入org.junit.internal.runners.model.EachTestNotifier;
导入org.junit.runner.Description;
导入org.junit.runner.notification.RunNotifier;
导入org.junit.runner.notification.StoppedByUserException;
导入org.junit.runners.BlockJUnit4ClassRunner;
导入org.junit.runners.model.FrameworkMethod;
导入org.junit.runners.model.InitializationError;
导入org.junit.runners.model.Statement;
公共类RetryRunner扩展了BlockJUnit4ClassRunner{
私人最终int retryCount=100;
专用int failedAttempts=0;
public RetryRunner(类klass)抛出初始化错误{
超级(klass);;
}
@凌驾
公共无效运行(最终运行通知程序通知程序){
EachTestNotifier testNotifier=新的EachTestNotifier(通知程序,
getDescription());
语句=类块(通知程序);
试一试{
语句。evaluate();
}捕获(假设违反异常e){
testNotifier.fireTestIgnored();
}捕获(由用户异常停止){
投掷e;
}捕获(可丢弃的e){
重试(testNotifier,语句,e);
}
}
@凌驾
受保护的void runChild(最终框架方法,RunNotifier通知程序){
描述=描述儿童(方法);
if(method.getAnnotation(Ignore.class)!=null){
通知程序。fireTestIgnored(说明);
}否则{
runTestUnit(methodBlock(方法)、说明、通知程序);
}
}
/**
*运行表示叶(又名原子)测试的{@link语句}。
*/
受保护的最终无效运行测试单元(语句、说明、,
运行通知程序(通知程序){
EachTestNotifier eachNotifier=新的EachTestNotifier(通知程序,说明)
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"));
}
}
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;
}
}