Volver a ejecutar la clase completa y no solo @Test en TestNG

9

He estado buscando stackoverflow durante algunos días, tratando de encontrar cómo volver a ejecutar una clase de prueba completa, y no solo un @Testpaso. Muchos dicen que esto no es compatible con TestNG y IRetryAnalyzer, mientras que algunos han publicado soluciones alternativas, eso realmente no funciona. ¿Alguien ha logrado hacerlo? Y solo para aclarar las razones de esto, con el fin de evitar respuestas que dicen que no es compatible con el propósito: TestNG es una herramienta no solo para desarrolladores. Lo que significa que también se utiliza desde probadores de sw para pruebas e2e. Las pruebas E2e pueden tener pasos que dependen cada uno del anterior. Entonces sí, es válido volver a ejecutar toda la clase de prueba, en lugar de simple @Test, lo cual se puede hacer fácilmente a través de IRetryAnalyzer.

Un ejemplo de lo que quiero lograr sería:

public class DemoTest extends TestBase {

@Test(alwaysRun = true, description = "Do this")
public void testStep_1() {
    driver.navigate().to("http://www.stackoverflow.com");
    Assert.assertEquals(driver.getCurrentUrl().contains("stackoverflow)"));

}

@Test(alwaysRun = true, dependsOnMethods = "testStep_1", description = "Do that")
public void testStep_2() {
    driver.press("button");
    Assert.assertEquals(true, driver.elementIsVisible("button"));

}

@Test(alwaysRun = true, dependsOnMethods = "testStep_2", description = "Do something else")
public void testStep_3() {
   driver.press("button2");
Assert.assertEquals(true, driver.elementIsVisible("button"));

}

}

Digamos que testStep_2falla, quiero volver a ejecutar class DemoTesty no solotestStep_2

gandalf_the_cool
fuente
¿Puedes mostrarnos la solución que no funciona?
AndiCover
Edite su pregunta, incluya una muestra y muéstrenos cuáles son sus expectativas. Eso ayudaría mucho a otros a darle una respuesta que cumpla con sus expectativas.
Krishnan Mahadevan
@AndiCover Enlaces a soluciones que no funcionan (o son soluciones que destruyen la lógica de testNG): stackoverflow.com/questions/25781098/… stackoverflow.com/questions/50241880/… stackoverflow.com/questions/53736621/…
gandalf_the_cool

Respuestas:

1

Bien, sé que probablemente quieras una propiedad fácil que puedas especificar en tu @BeforeClass o algo así, pero es posible que debamos esperar a que se implemente. Al menos tampoco pude encontrarlo.

Lo siguiente es feo como el infierno, pero creo que hace el trabajo, al menos en pequeña escala, se deja ver cómo se comporta en escenarios más complejos. Quizás con más tiempo, esto se puede mejorar en algo mejor.

Bien, entonces creé una clase de prueba similar a la tuya:

public class RetryTest extends TestConfig {

    public class RetryTest extends TestConfig {

        Assertion assertion = new Assertion();

        @Test(  enabled = true,
                groups = { "retryTest" },
                retryAnalyzer = TestRetry.class,
                ignoreMissingDependencies = false)
        public void testStep_1() {
        }

        @Test(  enabled = true,
                groups = { "retryTest" },
                retryAnalyzer = TestRetry.class,
                dependsOnMethods = "testStep_1",
                ignoreMissingDependencies = false)
        public void testStep_2() {
            if (fail) assertion.fail("This will fail the first time and not the second.");
        }

        @Test(  enabled = true,
                groups = { "retryTest" },
                retryAnalyzer = TestRetry.class,
                dependsOnMethods = "testStep_2",
                ignoreMissingDependencies = false)
        public void testStep_3() {
        }

        @Test(  enabled = true)
        public void testStep_4() {
            assertion.fail("This should leave a failure in the end.");
        }

    }


Tengo el Listeneren la súper clase solo en el caso de que me gustaría extender esto a otras clases, pero también puede configurar el oyente en su clase de prueba.

@Listeners(TestListener.class)
public class TestConfig {
   protected static boolean retrySuccessful = false;
   protected static boolean fail = true;
}


Tres de los 4 métodos anteriores tienen a RetryAnalyzer. Lo dejé testStep_4sin él para asegurarme de que lo que estoy haciendo a continuación no interfiera con el resto de la ejecución. Dicho RetryAnalyzerno volverá a intentarlo (tenga en cuenta que el método regresa false), pero hará lo siguiente:

public class TestRetry implements IRetryAnalyzer {

    public static TestNG retryTestNG = null;

    @Override
    public boolean retry(ITestResult result) {
        Class[] classes = {CreateBookingTest.class};

        TestNG retryTestNG = new TestNG();
        retryTestNG.setDefaultTestName("RETRY TEST");
        retryTestNG.setTestClasses(classes);
        retryTestNG.setGroups("retryTest");
        retryTestNG.addListener(new RetryAnnotationTransformer());
        retryTestNG.addListener(new TestListenerRetry());
        retryTestNG.run();

        return false;
    }

}


Esto creará una ejecución dentro de su ejecución. No interferirá con el informe, y tan pronto como termine, continuará con su ejecución principal. Pero "reintentará" los métodos dentro de ese grupo.

Sí, lo sé, lo sé. Esto significa que ejecutará su conjunto de pruebas para siempre en un bucle eterno. Por eso el RetryAnnotationTransformer. En él, eliminaremos el RetryAnalyzer de la segunda ejecución de esas pruebas:

public class RetryAnnotationTransformer extends TestConfig implements IAnnotationTransformer {

    @SuppressWarnings("rawtypes")
    @Override
    public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
        fail = false; // This is just for debugging. Will make testStep_2 pass in the second run.
        annotation.setRetryAnalyzer(null);
    }

}


Ahora tenemos el último de nuestros problemas. Nuestro conjunto de pruebas original no sabe nada sobre esa ejecución de "reintento" allí. Aquí es donde se pone realmente feo. Necesitamos decirle a nuestro reportero lo que acaba de suceder. Y esta es la parte que te animo a mejorar. Me falta el tiempo para hacer algo mejor, pero si puedo, lo editaré en algún momento.

Primero, necesitamos saber si la ejecución de retryTestNG fue exitosa. Probablemente hay un millón de formas de hacerlo mejor, pero por ahora esto funciona. Configuré un oyente solo para volver a intentar la ejecución. Puede verlo TestRetryarriba y consta de lo siguiente:

public class TestListenerRetry extends TestConfig implements ITestListener {

    (...)

    @Override
    public void onFinish(ITestContext context) {
        if (context.getFailedTests().size()==0 && context.getSkippedTests().size()==0) {
            successful = true;
        }
    }

}

Ahora el oyente de la suite principal, el que viste arriba en la superclase TestConfigverá si sucedió la ejecución y si salió bien y actualizará el informe:

public class TestListener extends TestConfig implements ITestListener , ISuiteListener {

    (...)

    @Override
    public void onFinish(ISuite suite) {

        if (TestRetry.retryTestNG != null) {

            for (ITestNGMethod iTestNGMethod : suite.getMethodsByGroups().get("retryTest")) {

                Collection<ISuiteResult> iSuiteResultList = suite.getResults().values();

                for (ISuiteResult iSuiteResult : iSuiteResultList) {

                    ITestContext iTestContext = iSuiteResult.getTestContext();
                    List<ITestResult> unsuccessfulMethods = new ArrayList<ITestResult>();

                    for (ITestResult iTestResult : iTestContext.getFailedTests().getAllResults()) {
                        if (iTestResult.getMethod().equals(iTestNGMethod)) {
                            iTestContext.getFailedTests().removeResult(iTestResult);
                            unsuccessfulMethods.add(iTestResult);
                        }
                    }

                    for (ITestResult iTestResult : iTestContext.getSkippedTests().getAllResults()) {
                        if (iTestResult.getMethod().equals(iTestNGMethod)) {
                            iTestContext.getSkippedTests().removeResult(iTestResult);
                            unsuccessfulMethods.add(iTestResult);
                        }
                    }

                    for (ITestResult iTestResult : unsuccessfulMethods) {
                        iTestResult.setStatus(1);
                        iTestContext.getPassedTests().addResult(iTestResult, iTestResult.getMethod());
                    }

                }

            }

        }


    }

}

El informe debe mostrar ahora 3 pruebas aprobadas (como fueron reintentadas) y una que falló porque no era parte de las otras 3 pruebas:

Reporte final


Sé que no es lo que estás buscando, pero te ayudo a que te sirva hasta que agreguen la funcionalidad a TestNG.

Rodrigo Vaamonde
fuente
¡Vaya! Se me olvidó agregar un condicional en la escucha principal para actualizar el informe final solo si sucedió el paquete Retry y fue exitoso. Agregado ahora.
Rodrigo Vaamonde