Nice programing

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

nicepro 2020. 10. 16. 08:03
반응형

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


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

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

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


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의 모든 메서드에 대해 코드를 실행하는 방법을 정의하는 방법을 자세히 설명합니다.


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

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

Now there is a better option. If you're using maven plugins like: surfire or failsefe there is an option to add parameter rerunFailingTestsCount SurFire Api. This stuff was implemented in the following ticket: Jira Ticket. In this case you don't need to write your custom code and plugin automatically amend test results report.
I see only one drawback of this approach: If some test is failed on Before/After class stage test won't be re-ran.


You have to write your own org.junit.runner.Runner and annotate your tests with @RunWith(YourRunner.class).


Proposed comment was written based ob this article with some additions.

Here, if some Test Case from your jUnit project gets "failure" or "error" result, this Test Case will be re-run one more time. Totally here we set 3 chance to get success result.

So, We need to create Rule Class and add "@Rule" notifications to your Test Class.

If you do not want to wright the same "@Rule" notifications for each your Test Class, you can add it to your abstract SetProperty Class(if you have it) and extends from it.

Rule Class:

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

Test Class:

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

참고URL : https://stackoverflow.com/questions/8295100/how-to-re-run-failed-junit-tests-immediately

반응형