TestNg失敗重跑—解決使用 dataProvider 引數化用例次數衝突問題
問題背景
在使用 testng 執行 UI 自動化用例時,由於 UI自動化的不穩定性,我們在測試的時候,往往會加上失敗重跑機制。在不使用 @DataProvider 提供用例引數化時,是不會有什麼問題,如果使用了的話就會出現多條用例都是失敗時,重跑機制只會執行第一次失敗的用例,其他用例的失敗重跑就不執行了。
如下:提供的兩組引數都是失敗時!(重跑的次數設定為2次)
從上圖中可以看出,第一次失敗的用例有重跑了2次,第二次失敗的用例就沒有重跑2次。
TestNg重跑機製程式碼實現
TestNg提供的重跑機制,實現思路如下:
- 建立一個實現類,實現
IRetryAnalyzer
介面,重寫該介面的retry
- 建立一個實現類,實現
IAnnotationTransformer
介面,重寫介面transform
方法,用於監聽所有的@Test 註解的測試方法。 - 在 TestNg 的 xml 檔案中配置監聽。
建立 IRetryAnalyzer 實現類
package com.ggf.testng.listener;
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
/**
* @Description: 失敗重試方法
* 用來監聽用例的執行情況,如果斷言失敗,或者程式碼出現錯誤了
* 都會被這個方法進行捕獲到,然後通過返回值來判斷是否進行重試。
* @Author: ggf
* @Date: 2020/07/20
*/
public class TestngRetry implements IRetryAnalyzer {
/**
* 最大的重跑次數
* 設定用例最多重跑多少次
*/
private int maxRetryCount = 2;
/**
* 當前的重跑的次數
*/
private int currentRetryCount = 1;
/**
* 複寫 IRetryAnalyzer 的方法,所有的用例執行完後的結果都會
* 封裝到這個物件ITestResult 傳入到 retry.xml 方法,通過這個方法
* 返回值來判斷是否需要重新執行用例。false :不重跑 true:重跑。
* @param iTestResult
* @return
*/
@Override
public boolean retry(ITestResult iTestResult) {
//如果retry方法返回為true--》執行重試機制
//如果返回是為false --》不會去執行重試
//什麼時候會執行到這裡??條件-->測試用例執行失敗
if(currentRetryCount <= maxRetryCount){
//運行了一次重試機制之後,我們就加1
//如果執行第一次重試機制-->用例執行成功了,用例的結果是pass的
//如果執行第一次重試機制-->用例執行成功了,第二次重試機制不會執行
System.out.println("重跑第【"+currentRetryCount+"】次!");
currentRetryCount++;
return true;
}else{
return false;
}
}
}
建立 IAnnotationTransformer 實現類
package com.ggf.testng.listener;
import org.testng.IAnnotationTransformer;
import org.testng.IRetryAnalyzer;
import org.testng.annotations.ITestAnnotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* @Description:
* 由於在使用重跑機制的時候需要在每個用例@Test註解新增 retryAnalyzer 屬性。
* 如果用例量過大的話,非常的麻煩,所以我們引入 testng 提供的監聽器類:IAnnotationTransformer
* 通過這個監聽器類來實現,動態的修改@Test註解屬性,我們就可以統一給 @Test 註解動態加上屬性retryAnalyzer 值。
* @Author: ggf
* @Date: 2020/07/20
*/
public class RetryListener implements IAnnotationTransformer {
@Override
public void transform(ITestAnnotation iTestAnnotation, Class aClass, Constructor constructor, Method method) {
//1、拿到@test註解的retryAnalyzer屬性物件
IRetryAnalyzer iRetryAnalyzer = iTestAnnotation.getRetryAnalyzer();
//2、如果@test的retryAnalyzer屬性沒有設定,iRetryAnalyzer-->null
if(iRetryAnalyzer == null){
iTestAnnotation.setRetryAnalyzer(TestngRetry.class);
}
}
}
xml 檔案配置監聽器
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="retry">
<test name="retryTest">
<classes>
<class name="com.ggf.testng.listener.RetryDemo"></class>
</classes>
</test>
<listeners>
<!--失敗重試監聽器-->
<listener class-name="com.ggf.testng.listener.RetryListener"></listener>
</listeners>
</suite>
測試類
package com.ggf.testng.listener;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/**
* @Description:
* @Author: ggf
* @Date: 2020/07/20
*/
public class RetryDemo {
@Test(dataProvider = "data")
public void testRetry(String data1, String data2) {
// 斷言兩個引數是否一樣
Assert.assertEquals(data1, data2);
}
@DataProvider
public Object[][] data() {
// 提供兩組測試引數
return new Object[][]{{"111","123"}, {"123", "1234"}};
}
}
失敗用例重跑問題重現
對於一個使用了dataProvider
的用例,因為這個用例是一個標記為@Test
的方法,會共用TestngRetry
的currentRetryCount
,即整個方法的所有引數化用例,總共只會重跑 2 次。例如一個引數化用例有 2 組引數,如果全部正確,每個用例只會輸出一次:
Test1: success
Test2: success
如果兩組引數的用例都失敗了,對於第一組引數是會重跑2次的(程式碼設定的是2次,不包含第一次),到了第二組引數就不會繼續重跑了,因為currentRetryCount
在第一組引數用例跑完,當前值就為 3 了,這個時候不滿足重跑條件,第二組引數用例失敗後就不會重跑了。
Test1: failed -> skipped
Test1: failed -> skipped
Test1: failed
Test2: failed
Test2: failed
至於這裡的 Test2 為什麼會跑 2 次,沒搞明白,正常來說應該是跑一次的,因為程式中 if(currentRetryCount <= maxRetryCount)
` 滿足才會重跑,但是這裡是 false 所以不會重跑才對。如果有大神們知道為什麼,希望不吝賜教,留個言。。。
問題解決方案
要解決上述的問題,需要在每組引數化用例結束(無論成功,失敗)後,重置 currentRetryCount
的值,讓當前的次數保持在初始化的狀態。
在實現類 TestngRetry 中加上一個重置的方法,如下:
package com.ggf.testng.listener;
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
/**
* @Description: 失敗重試方法
* 用來監聽用例的執行情況,如果斷言失敗,或者程式碼出現錯誤了
* 都會被這個方法進行捕獲到,然後通過返回值來判斷是否進行重試。
* @Author: ggf
* @Date: 2020/07/20
*/
public class TestngRetry implements IRetryAnalyzer {
/**
* 最大的重跑次數
* 設定用例最多重跑多少次
*/
private int maxRetryCount = 2;
/**
* 當前的重跑的次數
*/
private int currentRetryCount = 1;
/**
* 複寫 IRetryAnalyzer 的方法,所有的用例執行完後的結果都會
* 封裝到這個物件ITestResult 傳入到 retry.xml 方法,通過這個方法
* 返回值來判斷是否需要重新執行用例。false :不重跑 true:重跑。
* @param iTestResult
* @return
*/
@Override
public boolean retry(ITestResult iTestResult) {
//如果retry方法返回為true--》執行重試機制
//如果返回是為false --》不會去執行重試
//什麼時候會執行到這裡??條件-->測試用例執行失敗
if(currentRetryCount <= maxRetryCount){
//運行了一次重試機制之後,我們就加1
//如果執行第一次重試機制-->用例執行成功了,用例的結果是pass的
//如果執行第一次重試機制-->用例執行成功了,第二次重試機制不會執行
System.out.println("重跑第【"+currentRetryCount+"】次!");
currentRetryCount++;
return true;
}else{
return false;
}
}
/**
* 用於重置失敗重跑時的次數,還原到初始化的值
* 如果專案中是使用dataProvider註解來提供用例測試資料引數化的,
* 那麼每個@Test執行的時候都會共有重跑的次數。
* 例如:一個引數化用例有 3 組引數,如果全部正確,結果是:全部通過
* 如果第一組引數,第一次失敗(第二次成功,這裡就用掉了一次重跑的次數,currentRetryCount 就+1了)
* 接著第二組引數每次執行都失敗,這個時候currentRetryCount=2, 那麼第二組引數也就只會執行一次重跑。
*/
public void reset() {
currentRetryCount = 1;
}
}
再新建一個監聽類 TestngListener
, 繼承 TestListenerAdapter
類,並重寫 onTestSuccess
和 onTestFailure
方法:
package com.ggf.testng.listener;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
import java.util.Iterator;
/**
* @Description:
* @Author: ggf
* @Date: 2020/07/20
*/
public class TestngListener extends TestListenerAdapter {
@Override
public void onTestSuccess(ITestResult iTestResult) {
super.onTestSuccess(iTestResult);
TestngRetry testngRetry = (TestngRetry)iTestResult.getMethod().getRetryAnalyzer();
testngRetry.reset();
}
@Override
public void onTestFailure(ITestResult iTestResult) {
super.onTestFailure(iTestResult);
// 每次dataProvider中的引數跑完一次,就重置一次當前的重跑次數,恢復到預設值,保證每個失敗的用例都能重跑設定的次數。
TestngRetry testngRetry = (TestngRetry)iTestResult.getMethod().getRetryAnalyzer();
testngRetry.reset();
}
}
再把新建的TestngListener
監聽類,配置到 xml 檔案中:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="retry">
<test name="retryTest">
<classes>
<class name="com.ggf.testng.listener.RetryDemo"></class>
</classes>
</test>
<listeners>
<!--失敗重試監聽器-->
<listener class-name="com.ggf.testng.listener.RetryListener"></listener>
<!--用例執行結果監聽-->
<listener class-name="com.ggf.testng.listener.TestngListener"></listener>
</listeners>
</suite>
再次執行的結果:
以上就是對於解決使用了dataProvider
用例中的每一個引數化用例,在不重置的情況下,用例重跑次數共用的問題。
最後我們加上重置操作後,失敗的用例都會重跑跑 2 次,無論最後成功還是失敗,都會重置 TestngRetry
中的currentRetryCount
以保證下一個引數化用例開始時,currentRetryCount
為初始狀態。
參考文章:https://ntflc.com/2018/10/18/TestNg-Retry-Failed-Tests-with-DataProvider/