다시 한 번 실행하려고 시도함으로써 모든 실패한 테스트에 두 번째 기회를 제공하는 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 에서 구현되었습니다 . 이 경우 사용자 정의 코드를 작성할 필요가 없으며 플러그인이 테스트 결과 보고서를 자동으로 수정합니다.
이 접근 방식의 한 가지 단점이 있습니다. 수업 전 / 후 단계 테스트에서 일부 테스트가 실패하면 다시 실행되지 않습니다.
답변
나에 관해서는 커스텀 러너를 더 유연한 솔루션으로 작성합니다. 위에 게시 된 솔루션 (코드 예제 포함)에는 두 가지 단점이 있습니다.
- @BeforeClass 단계에서 실패하면 테스트를 다시 시도하지 않습니다.
- 테스트 실행을 계산하는 방법은 약간 다릅니다 (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()
방법을 사용하여 테스트에서 시나리오에 액세스 할 수 있습니다 .