[java] 실패한 JUnit 테스트를 즉시 다시 실행하는 방법은 무엇입니까?

다시 한 번 실행하려고 시도함으로써 모든 실패한 테스트에 두 번째 기회를 제공하는 JUnit 규칙 또는 이와 유사한 방법이 있습니까?

배경 : JUnit으로 작성된 대규모 Selenium2-WebDriver 테스트 세트가 있습니다. 매우 공격적인 타이밍 (클릭 후 짧은 대기 시간 만)으로 인해 일부 테스트 (100 개 중 1 개, 항상 다른 테스트)가 실패 할 수 있습니다. 서버가 때때로 약간 느리게 응답하기 때문입니다. 하지만 대기 시간이 너무 길어서 테스트가 오래 걸리기 때문에 확실히 충분히 길어질 수는 없습니다.)-따라서 테스트가 1 초가 필요하더라도 녹색 인 것이이 사용 사례에 적합하다고 생각합니다. 시험.

물론 3 점 만점에 2 점을받는 것이 좋지만 (실패한 테스트를 3 번 ​​반복하고 두 테스트가 맞으면 올바른 것으로 간주) 이것은 향후 개선이 될 것입니다.



답변

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

a의 마음 TestRule은 IS base.evaluate()테스트 메소드를 호출한다. 그래서이 호출 주위에 재시도 루프를 넣습니다. 테스트 메서드에서 예외가 발생하면 (어설 션 실패는 실제로 AssertionError) 테스트가 실패한 것이므로 다시 시도합니다.

사용할 수있는 또 다른 것이 있습니다. 이 재시도 논리를 테스트 세트에만 적용 할 수 있습니다.이 경우 메서드의 특정 주석에 대한 테스트 위에 Retry 클래스를 추가 할 수 있습니다. Description메소드에 대한 주석 목록을 포함합니다. 이에 대한 자세한 내용은 @RunWith 또는 AOP를 사용하지 않고 각 JUnit @Test 메서드 전에 개별적으로 일부 코드를 실행하는 방법에 대한 내 답변을 참조하십시오 . .

사용자 지정 TestRunner 사용

이것이 CKuck의 제안이며, 자신 만의 Runner를 정의 할 수 있습니다. BlockJUnit4ClassRunner 를 확장 하고 runChild ()를 재정의 해야합니다 . 자세한 내용은 스위트에서 JUnit 메소드 규칙을 정의하는 방법에 대한 내 대답을 참조하십시오 . . 이 답변은 자체 Runner를 정의해야하는 Suite의 모든 메서드에 대해 코드를 실행하는 방법을 정의하는 방법을 자세히 설명합니다.


답변

이제 더 나은 옵션이 있습니다. surfire 또는 failsefe와 같은 maven 플러그인을 사용하는 경우 rerunFailingTestsCount SurFire Api 매개 변수를 추가하는 옵션이 있습니다 . 이 항목은 Jira Ticket 에서 구현되었습니다 . 이 경우 사용자 정의 코드를 작성할 필요가 없으며 플러그인이 테스트 결과 보고서를 자동으로 수정합니다.
이 접근 방식의 한 가지 단점이 있습니다. 수업 전 / 후 단계 테스트에서 일부 테스트가 실패하면 다시 실행되지 않습니다.


답변

나에 관해서는 커스텀 러너를 더 유연한 솔루션으로 작성합니다. 위에 게시 된 솔루션 (코드 예제 포함)에는 두 가지 단점이 있습니다.

  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);
    }
}


답변

직접 작성 org.junit.runner.Runner하고 테스트에 @RunWith(YourRunner.class).


답변

제안 된 의견은 일부 추가 사항과 함께이 기사를 기반으로 작성되었습니다 .

여기에서 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"));
    }
}


답변

이 답변은이 답변을 기반으로 합니다 .

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()방법을 사용하여 테스트에서 시나리오에 액세스 할 수 있습니다 .


답변