¿Hay alguna forma de tener una regla JUnit o algo similar que le dé a cada prueba que falla una segunda oportunidad, simplemente intentando ejecutarla una vez más?
Antecedentes: tengo un gran conjunto de pruebas de Selenium2-WebDriver escritas con JUnit. Debido a una sincronización muy agresiva (solo breves períodos de espera después de los clics), algunas pruebas (1 de cada 100, y siempre una diferente) pueden fallar porque el servidor a veces responde un poco más lento. Pero no puedo hacer que el período de espera sea tan largo que definitivamente sea lo suficientemente largo, porque entonces las pruebas durarán una eternidad.) - Así que creo que es aceptable para este caso de uso que una prueba sea verde incluso si necesita un segundo tratar.
Por supuesto, sería mejor tener una mayoría de 2 de 3 (repita una prueba fallida 3 veces y tómelas como correctas, si dos de las pruebas son correctas), pero esto sería una mejora futura.
Respuestas:
Puede hacer esto con TestRule . Esto le dará la flexibilidad que necesita. Una TestRule le permite insertar lógica alrededor de la prueba, por lo que implementaría el ciclo de reintento:
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"); } }
El corazón de a
TestRule
es elbase.evaluate()
, que llama a su método de prueba. Entonces, alrededor de esta llamada, pones un bucle de reintento. Si se lanza una excepción en su método de prueba (un error de aserción es en realidad unAssertionError
), entonces la prueba ha fallado y volverá a intentarlo.Hay otra cosa que puede ser útil. Es posible que solo desee aplicar esta lógica de reintento a un conjunto de pruebas, en cuyo caso puede agregar a la clase Reintentar encima de una prueba para una anotación particular en el método.
Description
contiene una lista de anotaciones para el método. Para obtener más información sobre esto, consulte mi respuesta a ¿Cómo ejecutar un código antes de cada método JUnit @Test individualmente, sin usar @RunWith ni AOP? .Usando un TestRunner personalizado
Esta es la sugerencia de CKuck, puedes definir tu propio Runner. Necesita extender BlockJUnit4ClassRunner y anular runChild (). Para obtener más información, consulte mi respuesta a ¿Cómo definir la regla del método JUnit en una suite? . Esta respuesta detalla cómo definir cómo ejecutar código para cada método en una Suite, para lo cual debe definir su propio Runner.
fuente
Ahora hay una mejor opción. Si está utilizando complementos de maven como: surfire o failsefe, hay una opción para agregar el parámetro
rerunFailingTestsCount
SurFire Api . Esto se implementó en el siguiente ticket: Jira Ticket . En este caso, no es necesario que escriba su código personalizado y el complemento modifique automáticamente el informe de resultados de la prueba.Solo veo un inconveniente de este enfoque: si alguna prueba falla en la etapa de antes / después de la clase, la prueba no se volverá a ejecutar.
fuente
En cuanto a mí, escribir un corredor personalizado, una solución más flexible. La solución que se publicó anteriormente (con ejemplo de código) tiene dos desventajas:
Es por eso que prefiero un enfoque más al escribir un corredor personalizado. Y el código del corredor personalizado podría estar siguiendo:
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); } }
fuente
Tienes que escribir el tuyo
org.junit.runner.Runner
y anotar tus pruebas con@RunWith(YourRunner.class)
.fuente
El comentario propuesto se escribió en base a este artículo con algunas adiciones.
Aquí, si algún caso de prueba de su proyecto jUnit obtiene un resultado de "falla" o "error", este caso de prueba se volverá a ejecutar una vez más. Totalmente aquí establecemos 3 posibilidades de obtener un resultado exitoso.
Por lo tanto, necesitamos crear una clase de reglas y agregar notificaciones "@Rule" a su clase de prueba .
Si no desea escribir las mismas notificaciones de "@Rule" para cada clase de prueba, puede agregarla a su clase SetProperty abstracta (si la tiene) y extenderla.
Clase de regla:
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; } }; } }
Clase de prueba:
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")); } }
fuente
Esta respuesta se basa en esta respuesta .
Si necesita que su
ActivityScenario
(y su Actividad) se vuelva a crear antes de cada ejecución, puede iniciarla utilizando try-with-resources. AActivityScenario
continuación, se cerrará automáticamente después de cada intento.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; } }
Luego puede acceder a su escenario en sus pruebas utilizando el
getScenario()
método.fuente