Czy istnieje sposób, aby mieć regułę JUnit lub coś podobnego, co daje każdemu negatywnemu testowi drugą szansę, po prostu próbując uruchomić go jeszcze raz.
Kontekst: Mam duży zestaw testów Selenium2-WebDriver napisanych w JUnit. Z powodu bardzo agresywnego timingu (tylko krótkie okresy oczekiwania po kliknięciach) niektóre testy (1 na 100 i zawsze inny) mogą się nie powieść, ponieważ serwer czasami reaguje nieco wolniej. Ale nie mogę tak wydłużyć okresu oczekiwania, aby był na pewno wystarczająco długi, ponieważ wtedy testy potrwają wiecznie.) - Więc myślę, że w tym przypadku użycia jest dopuszczalne, aby test był zielony, nawet jeśli potrzebuje sekundy próbować.
Oczywiście lepiej byłoby mieć większość 2 z 3 (powtórzyć 3 razy negatywny test i przyjąć je jako poprawne, jeśli dwa testy są poprawne), ale byłaby to przyszła poprawa.
Odpowiedzi:
Możesz to zrobić za pomocą TestRule . Zapewni to elastyczność, której potrzebujesz. TestRule umożliwia wstawienie logiki wokół testu, więc zaimplementowałbyś pętlę ponawiania:
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"); } }
Sercem a
TestRule
jestbase.evaluate()
, który wywołuje Twoją metodę testową. Więc wokół tego wywołania umieszczasz pętlę ponawiania. Jeśli w metodzie testowej zostanie zgłoszony wyjątek (awaria potwierdzenia to w rzeczywistości anAssertionError
), oznacza to, że test się nie powiódł i spróbujesz ponownie.Jest jeszcze jedna rzecz, która może się przydać. Możesz chcieć zastosować tę logikę ponawiania tylko do zestawu testów, w takim przypadku możesz dodać do klasy Retry powyżej testu dla określonej adnotacji w metodzie.
Description
zawiera listę adnotacji dla metody. Aby uzyskać więcej informacji na ten temat, zobacz moją odpowiedź na temat Jak uruchomić kod przed każdą metodą JUnit @Test indywidualnie, bez użycia @RunWith ani AOP? .Korzystanie z niestandardowego narzędzia TestRunner
To jest sugestia CKucka, możesz zdefiniować własnego Runnera. Musisz rozszerzyć BlockJUnit4ClassRunner i nadpisać runChild (). Aby uzyskać więcej informacji, zobacz moją odpowiedź na temat Jak zdefiniować regułę metody JUnit w zestawie? . Ta odpowiedź zawiera szczegółowe informacje, jak zdefiniować sposób uruchamiania kodu dla każdej metody w pakiecie, dla którego musisz zdefiniować własnego Runnera.
źródło
Teraz jest lepsza opcja. Jeśli używasz wtyczek maven takich jak: surfire lub failsefe, istnieje możliwość dodania parametru
rerunFailingTestsCount
SurFire Api . Te rzeczy zostały zaimplementowane w następującym bilecie: Jira Ticket . W takim przypadku nie musisz pisać własnego kodu, a wtyczka automatycznie zmienia raport wyników testu.Widzę tylko jedną wadę tego podejścia: jeśli jakiś test nie powiedzie się na etapie przed / po zajęciach, test nie będzie powtórzony.
źródło
Jak dla mnie pisząc niestandardowy biegacz bardziej elastyczne rozwiązanie. Powyższe rozwiązanie (z przykładem kodu) ma dwie wady:
Dlatego wolę bardziej podejście do pisania niestandardowego runnera. A kod niestandardowego runnera może wyglądać następująco:
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); } }
źródło
Musisz napisać własne
org.junit.runner.Runner
i opatrzyć adnotacje@RunWith(YourRunner.class)
.źródło
Proponowany komentarz został napisany na podstawie tego artykułu z pewnymi dodatkami.
W tym przypadku, jeśli jakiś przypadek testowy z projektu jUnit otrzyma wynik „niepowodzenie” lub „błąd”, ten przypadek testowy zostanie ponownie uruchomiony jeszcze raz. Całkowicie tutaj ustawiliśmy 3 szanse na uzyskanie wyniku sukcesu.
Dlatego musimy utworzyć klasę reguł i dodać powiadomienia „@Rule” do Twojej klasy testowej .
Jeśli nie chcesz tworzyć tych samych powiadomień „@Rule” dla każdej klasy testowej, możesz dodać ją do swojej abstrakcyjnej klasy SetProperty (jeśli ją masz) i rozszerzyć ją.
Klasa reguły:
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; } }; } }
Klasa testowa:
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")); } }
źródło
Ta odpowiedź jest zbudowana na tej odpowiedzi .
Jeśli chcesz, aby Twoja
ActivityScenario
(i Twoja Aktywność) były odtwarzane przed każdym biegiem, możesz je uruchomić za pomocą try-with-resources.ActivityScenario
Zostanie zamknięte automatycznie po każdej próbie.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; } }
Następnie możesz uzyskać dostęp do swojego scenariusza w testach za pomocą
getScenario()
metody.źródło