问题情况
先说下问题情况,最近在做testNG与selenium集成做自动化测试的问题。
因为如果将testNG做UI 测试的话,很多情况下可能测试是失败的,但是这些失败可能是一些其他的问题导致的,可能是脚本的问题或者是网络环境不稳定导致的,所以我们需要重新尝试运行这个失败的测试用例。
testNG倒是没有直接的retry testcase的功能,不过它却提供了很多的接口,我们可以实现这些接口来得到retry的效果。
在google上看到淘宝的QA项目组采用Ruby语言将testNG的源代码修改了retry的功能,然后又重新build后这样做的。这是一个solution,但是我不推荐。原因有两个:
1,修改的jar包是针对指定的testNG版本的,所以如果我们需要体验testNG的新版本功能,这个jar可能就需要在源码基本上重新build有点 不太合适,详细地址是:https://github.com/NetEase/Dagger/wiki/Retry-Failed-Or-Skipped-Testcases
2,该种修改的方法只能使用在testcase级别上,如果需要针对所有的testNG的testsuite都是用这种特性,可能就需要每个testcase都表明他们是使用这个retry功能,有点代码亢余。像这样在testcase中声明retry的类:
import org.apache.log4j.Logger; import org.testng.Assert; import org.testng.annotations.Test; import com.hp.baserunner.RetryFail; import com.hp.pop.DemoPage; public class DemoRun { private static Logger log=Logger.getLogger(DemoRun.class); @Test(retryAnalyzer=RetryFail.class)// 这里声明retry的类,可以看到如果这样每个testcase可能都需要这样做,代码是不是有点多啊 :( public void demoTest() { DemoPage dp=new DemoPage(); dp.demoTest(); } @Test public void demoTest2() { DemoPage dp2=new DemoPage(); dp2.demoTest2(); } }
既然是框架这样写肯定就有点不太合适了。
框架设计使用
testNG中的对应的提供这些功能的接口有这些:
Interface IRetryAnalyzer 这个就是retrytestcase的一个接口,然后impletment这个接口后实现相应的方法即可:
有一个类 RetryAnalyzerCount 已经实现了以上的这个接口的方法:
package org.testng.util; import org.testng.IRetryAnalyzer; import org.testng.ITestResult; import java.util.concurrent.atomic.AtomicInteger; /** * An implementation of IRetryAnalyzer that allows you to specify * the maximum number of times you want your test to be retried. * * @author tocman@gmail.com (Jeremie Lenfant-Engelmann) */ public abstract class RetryAnalyzerCount implements IRetryAnalyzer { // Default retry once. AtomicInteger count = new AtomicInteger(1); /** * Set the max number of time the method needs to be retried. * @param count */ protected void setCount(int count) { this.count.set(count); } /** * Retries the test if count is not 0. * @param result The result of the test. */ @Override public boolean retry(ITestResult result) { boolean retry = false; if (count.intValue() > 0) { retry = retryMethod(result); count.decrementAndGet(); } return retry; } /** * The method implemented by the class that test if the test * must be retried or not. * @param result The result of the test. * @return true if the test must be retried, false otherwise. */ public abstract boolean retryMethod(ITestResult result); }
所以从上面可以看出,如果直接使用继承这个RetryAnalyzerCount 类还是省不少事,直接就可以使用了。
Class TestListenerAdapter
IConfigurationListener, IConfigurationListener2, org.testng.internal.IResultListener, org.testng.internal.IResultListener2, ITestListener, ITestNGListener
上面的是另一个类实现了retry的操作的类。这里不使用。
我们今天所使用的是IRetryAnalyzer 接口的,代码如下:
package com.com.baserunner; import org.testng.IRetryAnalyzer; import org.testng.ITestResult; /** * @author sumeetmisri@gmail.com * @modify alterhu2020@gmail.com * @version 1.0 * @category * */ public class RetryFail implements IRetryAnalyzer { private final int m_maxRetries = 1; private final int m_sleepBetweenRetries = 1000; private int currentTry; private String previousTest = null; private String currentTest = null; public RetryFail() { currentTry = 0; } @Override public boolean retry(final ITestResult result) { // If a testcase has succeeded, this function is not called. boolean retValue = false; // Getting the max retries from suite. // String maxRetriesStr = result.getTestContext().getCurrentXmlTest().getParameter("maxRetries"); String maxRetriesStr = result.getTestContext().getSuite().getParameter("maxRetries"); int maxRetries = m_maxRetries; if(maxRetriesStr != null) { try { maxRetries = Integer.parseInt(maxRetriesStr); } catch (final NumberFormatException e) { System.out.println("NumberFormatException while parsing maxRetries from suite file." + e); } } // Getting the sleep between retries from suite.you can from the suite parameter String sleepBetweenRetriesStr = result.getTestContext().getSuite().getParameter("sleepBetweenRetries"); int sleepBetweenRetries = m_sleepBetweenRetries; if(sleepBetweenRetriesStr != null) { try { sleepBetweenRetries = Integer.parseInt(sleepBetweenRetriesStr); } catch (final NumberFormatException e) { System.out.println("NumberFormatException while parsing sleepBetweenRetries from suite file." + e); } } currentTest = result.getTestContext().getCurrentXmlTest().getName(); if (previousTest == null) { previousTest = currentTest; } if(!(previousTest.equals(currentTest))) { currentTry = 0; } if (currentTry < maxRetries &&!result.isSuccess()) { try { Thread.sleep(sleepBetweenRetries); } catch (final InterruptedException e) { e.printStackTrace(); } currentTry++; result.setStatus(ITestResult.SUCCESS_PERCENTAGE_FAILURE); retValue = true; } else { currentTry = 0; } previousTest = currentTest; // if this method returns true, it will rerun the test once again. return retValue; } }
还有一个lisetner需要加入到testNG的配置文件中:
package com.coma.baserunner; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import org.testng.IAnnotationTransformer; import org.testng.IRetryAnalyzer; import org.testng.annotations.ITestAnnotation; public class RetryListener implements IAnnotationTransformer { @SuppressWarnings("rawtypes") @Override public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) { IRetryAnalyzer retry = annotation.getRetryAnalyzer(); if (retry == null) { //annotation.setRetryAnalyzer(RetryAnalyzer.class); annotation.setRetryAnalyzer(RetryFail.class); } } }
然后在testNG的xml的配置文件中如下配置即可:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <suite name="FirstSuite" parallel="false" > <!-- <parameter name="configfile" value="/resources/config.properties"></parameter> --> <parameter name="excelpath" value="resources/TestData.xls"></parameter> <listeners> <listener class-name="com.com.baserunner.RetryListener"></listener> </listeners>
以上的配置方法没有任何问题,唯一的缺陷是,运行的时候testNG的报告中会将retry的testcase的次数也计算在内,所以可能造成,运行后的testcase数目不准确,关于这个问题网上也有人在讨论,可是一直都没有得到一个好的接解决。
最近觉得仔细看看testNG的源代码,看看能不能修改下对应的testNG的报告。使得结果显示的testcase数据与实际的一致,retry的testcase只计算最后一次运行成功的。
如果有结果,再更新。。。。。。。