首页 文章

TestNG重试失败的测试不会输出正确的测试结果

提问于
浏览
14

Setup: 我有一个扩展IRetryAnalyzer的类,并且实现了一个简单的重试逻辑,重写了以下方法:public boolean retry(ITestResult result){

我有另一个类,它扩展了类TestListenerAdapter,它重试失败的测试,直到它们通过或报告失败 . 我已经实现了我的逻辑覆盖以下方法:public void onTestFailure(ITestResult result){

Scenario: 我总共进行了10次测试 . 10次测试中有1次失败2次,并使用我的重试逻辑传递第3次尝试 . 测试结果显示如下:总测试:12,失败:2,跳过:0

我想要的是输出正确数量的测试运行 . 并且也忽略了自测试结束以来的2次失败 . 所以结果应该是这样的:总测试:10,失败:0,跳过:0

我在这里失踪了什么?我需要修改ITestResult对象吗?如果有,怎么样?

仅供参考:我能够使用JUnit实现这一点(实现TestRule接口) .

提前致谢 .

4 回答

  • 1

    请考虑以下测试结果 . 2次重试:

    • 通过=>总体还可以!

    • 失败,通过=>总体还可以!

    • 失败,失败,通过=>总体还可以!

    • 失败,失败,失败=>总体失败!

    我所做的是创建一个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's 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和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,您将遇到问题:

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

    对于失败的测试,重新评估数据提供者 . 因此,您将获得一个新的时间戳,同一个mehod的result1和result2将导致不同的哈希值 . 解决方案是在getId()方法中包含parameterIndex而不是参数,但似乎这样的值不包含在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
    
  • 6

    我试过,尝试过并尝试过 . 但现在终于有效了

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

    You test class

    @Listeners(value = MyTestListenerAdapter.class)
    
    public class Test  {
    
    //Your data provider
    @DataProvider
    
    @Test(retryAnalyzer = MyRetryAnalyzer.class)
    public void testMethod () {
        //your code goes here
     }
    
    }
    
  • 0

    我的方法 Build 在Morvader's answer之上,但增加了定义重试分析器的能力,这些分析器遵循实际失败测试的原始意图,即使该方法在一些重试之后已经过去 .

    我也没有发现需要倾向于onFinish()方法中的测试次数,数字在maven-surefire-plugin版本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;
        }
    }
    
  • 2

    我用这种方法:

    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 Configuration:

    这是你应该配置“覆盖”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>
    

相关问题