web-dev-qa-db-ja.com

失敗したJUnitテストをすぐに再実行する方法は?

もう一度実行するだけで、JUnitルールなど、失敗したすべてのテストに2回目のチャンスを与える同様の方法を使用する方法はありますか。

背景:JUnitで書かれたSelenium2-WebDriverテストの大規模なセットがあります。非常に攻撃的なタイミング(クリック後の短い待機時間のみ)により、一部のテスト(100のうち1つ、常に異なるテスト)サーバーの応答が少し遅くなることがあるため失敗します。しかし、テストが永遠にかかるため、待機期間を十分に長くすることはできません。)-だから、このユースケースでは許容できると思います2回目の試行が必要な場合でも、テストは緑色です

もちろん、3つのうち2つを過半数にして(失敗したテストを3回繰り返し、2つのテストが正しければ、それらを正しいと見なしてください)、これは将来の改善になります。

76
Ralph

TestRule でこれを行うことができます。これにより、必要な柔軟性が得られます。 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");
    }
}
_

TestRuleの中心はbase.evaluate()です。これはテストメソッドを呼び出します。したがって、この呼び出しの周りに再試行ループを配置します。テストメソッドで例外がスローされた場合(アサーションの失敗は実際にはAssertionErrorです)、テストは失敗し、再試行します。

役に立つかもしれない他の一つがあります。この再試行ロジックを一連のテストにのみ適用したい場合があります。その場合、メソッドの特定の注釈のテストの上にRetryクラスを追加できます。 Descriptionには、メソッドの注釈のリストが含まれています。これについての詳細は、 @ RunWithやAOPを使用せずに、各JUnit @Testメソッドの前に個別にコードを実行する方法 への回答を参照してください。

カスタムTestRunnerを使用する

これはCKuckの提案です。独自のランナーを定義できます。 BlockJUnit4ClassRunner を拡張し、runChild()をオーバーライドする必要があります。詳細については、 スイートでJUnitメソッドルールを定義する方法 に対する回答を参照してください。この回答では、独自のランナーを定義する必要があるスイート内のすべてのメソッドに対してコードを実行する方法を定義する方法について詳しく説明します。

99
Matthew Farwell

私については、カスタムランナーのより柔軟なソリューションを記述しています。上記に投稿したソリューション(コード例付き)には、2つの欠点があります。

  1. @BeforeClassステージで失敗した場合、テストを再試行しません。
  2. テストの実行方法は少し異なります(3回再試行すると、テストの実行結果が表示されます: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);
    }
}
18
user1459144

より良いオプションがあります。 surfireまたはfailefeのようなMavenプラグインを使用している場合、パラメーターrerunFailingTestsCountSurFire Api を追加するオプションがあります。このようなものは、次のチケットに実装されました: Jira Ticket 。この場合、カスタムコードとプラグインを作成してテスト結果レポートを自動的に修正する必要はありません。
このアプローチには1つの欠点しかありません。Before/ Afterクラスステージで何らかのテストが失敗した場合、テストは再実行されません。

17
user1459144

独自の_org.junit.runner.Runner_を記述し、@RunWith(YourRunner.class)でテストに注釈を付ける必要があります。

6
CKuck

提案されたコメントは、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"));
    }
}
3
Sergii