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).
Спасибо заранее.
Ответы
Ответ 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();
}
}
}
}
}
}