Ponowne uruchomienie pełnej klasy, a nie tylko @Test w TestNG

9

Przeglądam przepływ stosu od kilku dni, próbując znaleźć sposób na ponowne uruchomienie całej klasy testowej, a nie tylko @Testkrok. Wielu twierdzi, że nie jest to obsługiwane w TestNG i IRetryAnalyzerchociaż niektórzy opublikowali obejścia, to tak naprawdę nie działa. Czy ktoś to zrobił? Aby wyjaśnić powody, aby uniknąć odpowiedzi, które mówią, że nie jest to celowo obsługiwane: TestNG to narzędzie nie tylko dla programistów. Znaczenie to jest również używane z testerów SW do testowania e2e. Testy E2e mogą zawierać kroki, które zależą od poprzedniego. Więc tak, należy ponownie uruchomić całą klasę testową, a nie proste @Test, co można łatwo zrobić za pomocą IRetryAnalyzer.

Przykładem tego, co chcę osiągnąć, jest:

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

}

}

Powiedzmy, że to się testStep_2nie powiedzie, chcę ponownie uruchomić class DemoTesti nie tylkotestStep_2

gandalf_the_cool
źródło
Czy możesz nam pokazać obejście, które nie działa?
AndiCover,
Edytuj swoje pytanie, dołącz próbkę i pokaż nam, jakie są Twoje oczekiwania. Pomogłoby to w udzieleniu odpowiedzi, która spełni Twoje oczekiwania.
Krishnan Mahadevan,
@AndiCover Odnośniki do obejść, które nie działają (lub są obejścia, które niszczą logikę testNG): stackoverflow.com/questions/25781098/... stackoverflow.com/questions/50241880/… stackoverflow.com/questions/53736621/...
gandalf_the_cool

Odpowiedzi:

1

W porządku, wiem, że prawdopodobnie potrzebujesz prostej właściwości, którą możesz określić w swojej @BeforeClass lub coś w tym rodzaju, ale może być konieczne poczekanie na jej wdrożenie. Przynajmniej nie mogłem tego znaleźć.

Poniższe rzeczy są brzydkie jak diabli, ale myślę, że to działa, przynajmniej na małą skalę, pozostaje sprawdzić, jak się zachowuje w bardziej złożonych scenariuszach. Być może z czasem można to zmienić w coś lepszego.

Okej, więc stworzyłem klasę testową podobną do twojej:

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

    }


Mam Listenerklasę super w przypadku, gdybym chciał rozszerzyć ją na inne klasy, ale równie dobrze możesz ustawić słuchacza w klasie testowej.

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


Trzy z 4 powyższych metod mają RetryAnalyzer. Zostawiłem to testStep_4bez niego, aby upewnić się, że to, co robię dalej, nie zakłóci reszty egzekucji. Powiedział, RetryAnalyzerże tak naprawdę nie spróbuje ponownie (zauważ, że metoda zwraca false), ale wykona następujące czynności:

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

}


Spowoduje to utworzenie wykonania wewnątrz Twojego wykonania. Nie będzie bałaganu w raporcie, a jak tylko skończy, będzie kontynuował główne wykonanie. Ale „spróbuje” metod w tej grupie.

Tak, wiem, wiem. Oznacza to, że będziesz wykonywać swój zestaw testów na zawsze w wiecznej pętli. Właśnie dlatego RetryAnnotationTransformer. W nim usuniemy RetryAnalyzer z drugiego wykonania tych testów:

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

}


Teraz mamy ostatni z naszych problemów. Nasz oryginalny zestaw testów nic nie wie o tamtym wykonaniu „ponawiania”. Tutaj robi się naprawdę brzydko. Musimy powiedzieć naszemu reporterowi, co się właśnie wydarzyło. I to jest część, którą zachęcam do poprawy. Brakuje mi czasu na zrobienie czegoś ładniejszego, ale jeśli będę mógł, w pewnym momencie będę go edytować.

Najpierw musimy wiedzieć, czy wykonanie ponownej próby TestNG zakończyło się powodzeniem. Prawdopodobnie istnieje milion sposobów na zrobienie tego lepiej, ale na razie to działa. Skonfigurowałem odbiornik tylko do ponownej realizacji. Możesz to zobaczyć TestRetrypowyżej i składa się z następujących elementów:

public class TestListenerRetry extends TestConfig implements ITestListener {

    (...)

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

}

Teraz nasłuchujący pakietu głównego, ten, który widziałeś powyżej w superklasie TestConfig, sprawdzi, czy uruchomienie się odbyło i czy poszło dobrze, i zaktualizuje raport:

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

                }

            }

        }


    }

}

Raport powinien pokazywać teraz 3 testy zakończone pomyślnie (podczas ponownej próby) i jeden nieudany, ponieważ nie był częścią pozostałych 3 testów:

Raport końcowy


Wiem, że nie jest to, czego szukasz, ale pomagam ci, dopóki nie dodadzą funkcjonalności do TestNG.

Rodrigo Vaamonde
źródło
Ups .. Zapomniałem dodać warunku w głównym nasłuchiwaniu, aby aktualizować raport końcowy tylko wtedy, gdy wystąpił pakiet Ponów próbę i udało się. Dodano teraz
Rodrigo Vaamonde,