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


81

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

背景:JUnitで記述されたSelenium2-WebDriverテストの大規模なセットがあります。非常にアグレッシブなタイミング(クリック後の待機時間が短い)が原因で、サーバーの応答が少し遅くなることがあるため、一部のテスト(100回に1回、常に異なるテスト)が失敗する可能性があります。しかし、待機期間を長くすることはできません。テストには永遠に時間がかかるためです。)-したがって、このユースケースでは、1秒必要な場合でもテストが緑色であることが許容されると思います。試してみてください。

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


1
セレン2で待機時間を修正する必要はありません。WebDriverはページの読み込みを検出し、それに応じて待機する必要があります。:あなたは、いくつかのJavaScriptを実行するために、たとえば、他の何かが、ページの読み込みを待つしたい場合は、あなたが見る、WebDriverWaitクラスを使用する必要がありseleniumhq.org/docs/04_webdriver_advanced.htmlを。そうは言っても、GUIテストを再試行しても大丈夫だと思います。ほとんどの場合、明示的な待機時間は必要ないことを明確にしたかっただけです。
ティムビューテ2012年

それは本当だが、私は「罰金」しているいくつかの本当に、本当に貧しいサーバー上で働いてきたことも指摘しますが、彼らは持っている本当に特定のページインスタンスのタイムアップに長いスピンを、そのため、私はしたくありません失敗する。これは素晴らしい質問です、ありがとう。(もちろん、私はタイミングがあることを好むだろう常に一貫して、我々はそのためにプッシュするつもりだが、それまで、これを行う必要があります)
CGP

キュウリのrerun.txt機能を使用している場合は、ここで
Sugat Mankar 2015年

Cucumber rerun.txt機能を使用している場合は、こちらの
Sugat Mankar 2015年

回答:


107

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メソッドルールを定義する方法に対する私の回答を参照してください。この回答では、独自のランナーを定義する必要があるスイート内のすべてのメソッドのコードを実行する方法を定義する方法について詳しく説明します。


おかげで:ところで、これをどのように試すかについては、TestRuleはJUnitバージョン4.9以降に存在する機能です
Ralph

@Ralph実際、TestRuleは以前に導入されたMethodRuleの代替であり、約4.7 IIRCであるため、このソリューションは4.9より前に適用される可能性がありますが、わずかに異なります。
マシューファーウェル2011年

7
これは本当に役に立ちましたが、私の頭に浮かんだことがあります。retryCountとretriesは誤解を招く名前かもしれません。再試行が1の場合、彼はテストを実行し、失敗した場合は1回再試行すると想定しますが、そうではありません。変数はおそらくmaxTriesと呼ばれるべきです。
Thomas M.

1
@MatthewFarwell:これでアクティビティが再開されますか?方法はありますか、それはできますか?
Basim Sherif 2016

4
このメソッドを使用すると、テストインスタンスを再作成せずにテストの再実行が実行されるという制約があります。つまり、テストクラス(またはスーパークラス)のインスタンスフィールドは再初期化されず、以前の実行からの状態が残る可能性があります。
Jonah Graham

19

今、より良いオプションがあります。surfireやfailesefeなどのMavenプラグインを使用している場合は、パラメーターrerunFailingTestsCount SurFireApiを追加するオプションがあります。このようなものは、次のチケットに実装されました:Jiraチケット。この場合、カスタムコードを作成する必要はなく、プラグインはテスト結果レポートを自動的に修正します。
このアプローチの欠点は1つだけです。クラスの前/後の段階でテストが失敗した場合、テストは再実行されません。


Mavenのコマンドライン上の例は:-Dsurefire.rerunFailingTestsCount = 2インストールMVN
activout.se

18

私はカスタムランナーのより柔軟なソリューションを書いています。上記に投稿したソリューション(コード例を含む)には、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);
    }
}

2
問題は、AfterClassメソッドでテストが失敗した場合です。
user1050755 2014年

1
問題はありません。指定されたランナーでテストを実行するサンプルテストを作成しましたが、問題なく動作しているようです。@ RunWith(RetryRunner.class)public class TestSample {private static int i = 0; @AfterClass public static void testBefore(){System.out.println( "テスト前"); i ++; if(i <2){fail( "Fail"); }}}
user1459144 2014

6

独自に作成しorg.junit.runner.Runner、テストに@RunWith(YourRunner.class)。で注釈を付ける必要があります。


5

提案されたコメントは、いくつかの追加を加えたこの記事に基づいて書かれました。

ここで、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"));
    }
}

0

この答えは、この答えに基づいています

あなたが必要な場合はActivityScenario、各実行の前に再作成する(とあなたの活動を)、あなたは、try-と、リソースを使用して、それを起動することができます。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()メソッドを使用してテストでシナリオにアクセスできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.