問題情況
先說下問題情況,最近在做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只計算最后一次運行成功的。
如果有結果,再更新。。。。。。。