Подтвердить что ты не робот

TestNG повторная попытка неудачных тестов не выводит правильные результаты теста

Настройка: У меня есть класс, который расширяет IRetryAnalyzer и реализовал простую логику повтора, переопределяя следующий метод:   public boolean retry (результат ITestResult) {

У меня есть еще один класс, который расширяет класс TestListenerAdapter, который повторяет те тесты, которые не удались, пока они не пройдут или не сообщают о сбоях. Я реализовал свою логику, переопределяя следующий метод:   public void onTestFailure (результат ITestResult) {

Сценарий: У меня в общей сложности 10 тестов. 1 из 10 тестов терпит неудачу 2 раза и передают третью попытку с моей логикой повтора. Результаты теста показывают следующее: Всего тестов: 12, Не удалось: 2, Пропущено: 0

Мне бы хотелось, чтобы вывести правильное количество тестов. А также проигнорируйте 2 отказа, поскольку тест прошел в конце. Поэтому результат должен выглядеть примерно так: Всего тестов: 10, Ошибка: 0, Пропущено: 0

Что мне здесь не хватает? Нужно ли мне модифицировать объект ITestResult? Если да, то как?

FYI: Я смог добиться этого с помощью JUnit (реализация интерфейса TestRule).

Спасибо заранее.

4b9b3361

Ответ 1

Пожалуйста, рассмотрите следующие результаты теста с макс. 2 Повторные попытки:

  • Прошел = > В целом нормально!
  • Failed, Passed = > В целом нормально!
  • Failed, Failed, Passed = > В целом нормально!
  • Сбой, сбой, сбой = > Общий сбой!

Что я сделал, так это создать прослушиватель TestNg, который расширяет поведение по умолчанию и делает некоторую магию после завершения всех тестов.

public class FixRetryListener extends TestListenerAdapter {

    @Override
    public void onFinish(ITestContext testContext) {
        super.onFinish(testContext);

        // List of test results which we will delete later
        List<ITestResult> testsToBeRemoved = new ArrayList<>();

        // collect all id from passed test
        Set <Integer> passedTestIds = new HashSet<>();
        for (ITestResult passedTest : testContext.getPassedTests().getAllResults()) {
            passedTestIds.add(TestUtil.getId(passedTest));
        }

        Set <Integer> failedTestIds = new HashSet<>();
        for (ITestResult failedTest : testContext.getFailedTests().getAllResults()) {

            // id = class + method + dataprovider
            int failedTestId = TestUtil.getId(failedTest);

            // if we saw this test as a failed test before we mark as to be deleted
            // or delete this failed test if there is at least one passed version
            if (failedTestIds.contains(failedTestId) || passedTestIds.contains(failedTestId)) {
                testsToBeRemoved.add(failedTest);
            } else {
                failedTestIds.add(failedTestId);
            }
        }

        // finally delete all tests that are marked
        for (Iterator<ITestResult> iterator = testContext.getFailedTests().getAllResults().iterator(); iterator.hasNext(); ) {
            ITestResult testResult = iterator.next();
            if (testsToBeRemoved.contains(testResult)) {
                iterator.remove();
            }
        }

    }

}

В основном я делаю 2 вещи:

  • Соберите все пройденные тесты. Если я столкнулся с неудачным тестом с хотя бы одним прошедшим тестом, я удаляю неудавшийся тест (это будет охватывать случаи 2 и 3 сверху).
  • Итерация по всем неудачным тестам. Если я столкнулся с неудавшимся тестом, который ранее не удался, я удаляю текущий неудачный результат. (Это будет охватывать случаи 3 и 4 на самом деле). Это также означает, что я сохраню только первый неудачный результат, если есть несколько неудачных результатов.

Чтобы идентифицировать testresult, я использую следующую простую хэш-функцию:

public class TestUtil {

    public static int getId(ITestResult result) {
        int id = result.getTestClass().getName().hashCode();
        id = 31 * id + result.getMethod().getMethodName().hashCode();
        id = 31 * id + (result.getParameters() != null ? Arrays.hashCode(result.getParameters()) : 0);
        return id;
    }
}

Этот подход действительно работает и с dataproviders, но имеет одно небольшое ограничение! Если вы используете dataproviders со случайными значениями, вы столкнетесь с проблемами:

@DataProvider(name = "dataprovider")
public Object[][] getData() {
    return new Object[][]{{System.currentTimeMillis()}};
}

В случае неудачных тестов датапарауэр переоценивается. Поэтому вы получите новую метку времени, а результат1 и result2 для одного и того же mehod приведут к различным значениям хэш-функции. Решением было бы включить параметрIndex в метод getId() вместо параметров, но, похоже, такое значение не включено в ITestResult.

См. этот простой пример как доказательство концепции:

@Listeners(value = FixRetryListener.class)
public class SimpleTest {

    private int count = 0;

    @DataProvider(name = "dataprovider")
    public Object[][] getData() {
        return new Object[][]{{"Run1"},{"Run2"}};
    }

    @Test(retryAnalyzer = RetryAnalyzer.class, dataProvider = "dataprovider")
    public void teste(String testName) {
        count++;
        System.out.println("---------------------------------------");
        System.out.println(testName + " " + count);
        if (count % 3 != 0) {
            Assert.fail();
        }

        count = 0;
    }

}

Уступит:

Total tests run: 2, Failures: 0, Skips: 0

Ответ 2

Я пытался, пытался и пытался. Но теперь, наконец, это сработало

MyRetryAnalyzer.java

import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;

import java.util.concurrent.atomic.AtomicInteger;


public class MyRetryAnalyzer implements IRetryAnalyzer {
private static int MAX_RETRY_COUNT = 3;

AtomicInteger count = new AtomicInteger(MAX_RETRY_COUNT);

public boolean isRetryAvailable() {
    return (count.intValue() > 0);
}

@Override
public boolean retry(ITestResult result) {
    boolean retry = false;
    if (isRetryAvailable()) {
        System.out.println("Going to retry test case: " + result.getMethod() + ", " + (MAX_RETRY_COUNT - count.intValue() + 1) + " out of " + MAX_RETRY_COUNT);
        retry = true;
        count.decrementAndGet();
    }
    return retry;
}
}

MyTestListenerAdapter.java

import org.testng.*;

import java.util.*;


public class MyTestListenerAdapter extends TestListenerAdapter {
    @Override
    public void onTestFailure(ITestResult result) {
        if (result.getMethod().getRetryAnalyzer() != null) {
            MyRetryAnalyzer retryAnalyzer = (MyRetryAnalyzer)result.getMethod().getRetryAnalyzer();

            if(retryAnalyzer.isRetryAvailable()) {
            } else {
                result.setStatus(ITestResult.FAILURE);
            }
            Reporter.setCurrentTestResult(result);
        }
    }

@Override
    public void onFinish(ITestContext context) {
        Iterator<ITestResult> failedTestCases =context.getFailedTests().getAllResults().iterator();
        while (failedTestCases.hasNext()) {
            System.out.println("failedTestCases");
            ITestResult failedTestCase = failedTestCases.next();
            ITestNGMethod method = failedTestCase.getMethod();
            if (context.getFailedTests().getResults(method).size() > 1) {
                System.out.println("failed test case remove as dup:" + failedTestCase.getTestClass().toString());
                failedTestCases.remove();
            } else {

                if (context.getPassedTests().getResults(method).size() > 0) {
                    System.out.println("failed test case remove as pass retry:" + failedTestCase.getTestClass().toString());
                    failedTestCases.remove();
                }
            }
        }
    }
}

Вы тестируете класс

@Listeners(value = MyTestListenerAdapter.class)

public class Test  {

//Your data provider
@DataProvider

@Test(retryAnalyzer = MyRetryAnalyzer.class)
public void testMethod () {
    //your code goes here
 }

}

Ответ 3

Мой подход основан на ответе Morvader, но добавляет возможность определять анализаторы повторов, которые соответствуют первоначальному намерению фактически провалить тест, даже если метод прошел после некоторого повторы.

Я также не нашел нужды стремиться к количеству тестов в методе onFinish(), цифры выглядели отлично в maven-surefire-plugin version 2.18

RetryListenerAdapter

public class RetryListenerAdapter extends TestListenerAdapter {

    @Override
    public void onTestFailure(ITestResult tr) {
        IRetryAnalyzer retryAnalyzer = tr.getMethod().getRetryAnalyzer();
        if (retryAnalyzer == null || !(retryAnalyzer instanceof IRetryAnalyzerWithSkip)) {
            super.onTestFailure(tr);
        } else if (((IRetryAnalyzerWithSkip) retryAnalyzer).isRetryable()) {
            tr.setStatus(ITestResult.SKIP);
            super.onTestSkipped(tr);
        } else {
            super.onTestFailure(tr);
        }
    }
}

IRetryAnalyzerWithSkip

public interface IRetryAnalyzerWithSkip extends IRetryAnalyzer {
    boolean isRetryable();
}

Retry

public class Retry implements IRetryAnalyzerWithSkip {
    private int retryCount = 0;
    private int maxRetryCount = 3;

    public boolean retry(ITestResult result) {

        if (retryCount < maxRetryCount) {
            retryCount++;
            return true;
        }
        return false;
    }

    @Override
    public boolean isRetryable() {
        return retryCount < maxRetryCount;
    }
}

Ответ 4

Я использую этот подход:

ListenerApadter

public class MyTestListenerAdapter extends TestListenerAdapter {
    @Override
    public void onTestFailure(ITestResult result) {
        if (result.getMethod().getRetryAnalyzer() != null) {
            MyRetryAnalyzer retryAnalyzer = (MyRetryAnalyzer)result.getMethod().getRetryAnalyzer();

            if(retryAnalyzer.isRetryAvailable()) {
                result.setStatus(ITestResult.SKIP);
            } else {
                result.setStatus(ITestResult.FAILURE);
            }
            Reporter.setCurrentTestResult(result);
        }
    }

   @Overrride
   public void onFinish(ITestContext context) {
     Iterator<ITestResult> failedTestCases =context.getFailedTests().getAllResults().iterator();
    while (failedTestCases.hasNext()) {
        System.out.println("failedTestCases");
        ITestResult failedTestCase = failedTestCases.next();
        ITestNGMethod method = failedTestCase.getMethod();
        if (context.getFailedTests().getResults(method).size() > 1) {
            System.out.println("failed test case remove as dup:" + failedTestCase.getTestClass().toString());
            failedTestCases.remove();
        } else {

            if (context.getPassedTests().getResults(method).size() > 0) {
                System.out.println("failed test case remove as pass retry:" + failedTestCase.getTestClass().toString());
                failedTestCases.remove();
            }
        }
    }
   }
}

RetryAnalizer

public class MyRetryAnalyzer implements IRetryAnalyzer {
    private static int MAX_RETRY_COUNT = 3;

    AtomicInteger count = new AtomicInteger(MAX_RETRY_COUNT);

    public boolean isRetryAvailable() {
        return (count.intValue() > 0);
    }

    @Override
    public boolean retry(ITestResult result) {
        boolean retry = false;
        if (isRetryAvailable()) {
            System.out.println("Going to retry test case: " + result.getMethod() + ", " + (MAX_RETRY_COUNT - count.intValue() + 1) + " out of " + MAX_RETRY_COUNT);
            retry = true;
            count.decrementAndGet();
        }
        return retry;
    }
}

POM.xml → Конфигурация Surefire:

Здесь вы должны настроить "перезаписать" верный приёмник, у которого есть свои счетчики.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.18.1</version>
  <configuration>
    <suiteXmlFiles><suiteXmlFile>${basedir}/testng.xml</suiteXmlFile></suiteXmlFiles>
 <properties> 
   <property>
    <name>listener</name>
    <value>Utils.MyTestListenerAdapter,Utils.MyRetryAnalizer</value>
   </property>
 </properties>

Ответ 5

Я внедряю itestlistener и хочу проверить, есть ли у пропущенного теста соответствующий сбойный тест, если этот тест удален из списка пропущенных тестов.

package testNg;

import java.awt.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;

import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
import org.testng.ITestNGMethod;

public class ListnerDemo implements ITestListener {

    public String TestName ;



    @Override
    public void onTestFailure(ITestResult result) {
        System.out.println("Test failed is  " +result.getName());
        TestName = result.getName();

    }
    @Override
    public void onFinish(ITestContext context) {
         Iterator<ITestResult> failedTestCases =context.getFailedTests().getAllResults().iterator();
            while (failedTestCases.hasNext()) {
                System.out.println("failedTestCases");
                ITestResult failedTestCase = failedTestCases.next();
                ITestNGMethod method = failedTestCase.getMethod();
                if (context.getFailedTests().getResults(method).size() > 1) {
                    System.out.println("failed test case remove as dup:" + failedTestCase.getTestClass().toString());
                    failedTestCases.remove();
                } else {

                    if (context.getPassedTests().getResults(method).size() > 0) {
                        System.out.println("failed test case remove as pass retry:" + failedTestCase.getTestClass().toString());
                        failedTestCases.remove();
                    }

                    Iterator<ITestResult> SkippedTestCases = context.getSkippedTests().getAllResults().iterator();
                        while ( SkippedTestCases.hasNext()){
                            System.out.println("failedTestCases");
                            ITestResult SkippedTest = SkippedTestCases.next();
                            ITestNGMethod methodA = SkippedTest.getMethod();
                            String TestSkipp = SkippedTest.getMethod().getQualifiedName();
                            System.out.println("Removing test case = " +TestName);
                            System.out.println("Skipped test case name is "+TestSkipp);
                            if(TestSkipp.contains(TestName)) {
                                SkippedTestCases.remove();
                            }


                        }

                }
            }
        }

    }