1. 程式人生 > >TestNg失敗重跑—解決使用 dataProvider 引數化用例次數衝突問題

TestNg失敗重跑—解決使用 dataProvider 引數化用例次數衝突問題

## 問題背景 在使用 testng 執行 UI 自動化用例時,由於 UI自動化的不穩定性,我們在測試的時候,往往會加上失敗重跑機制。在不使用 @DataProvider 提供用例引數化時,是不會有什麼問題,如果使用了的話就會出現多條用例都是失敗時,重跑機制只會執行第一次失敗的用例,其他用例的失敗重跑就不執行了。 如下:提供的兩組引數都是失敗時!(重跑的次數設定為2次) ![](https://javage.github.io/images/blog/retry-1.png) 從上圖中可以看出,第一次失敗的用例有重跑了2次,第二次失敗的用例就沒有重跑2次。 ## TestNg重跑機制程式碼實現 TestNg提供的重跑機制,實現思路如下: - 建立一個實現類,實現 `IRetryAnalyzer` 介面,重寫該介面的 `retry`方法,定義失敗重跑的規則。 - 建立一個實現類,實現 `IAnnotationTransformer` 介面,重寫介面 `transform` 方法,用於監聽所有的@Test 註解的測試方法。 - 在 TestNg 的 xml 檔案中配置監聽。 ### 建立 IRetryAnalyzer 實現類 ```java 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 實現類 ```java 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 ``` ### 測試類 ```java 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 組引數,如果全部正確,每個用例只會輸出一次: ```java Test1: success Test2: success ``` 如果兩組引數的用例都失敗了,對於第一組引數是會重跑2次的(程式碼設定的是2次,不包含第一次),到了第二組引數就不會繼續重跑了,因為`currentRetryCount`在第一組引數用例跑完,當前值就為 3 了,這個時候不滿足重跑條件,第二組引數用例失敗後就不會重跑了。 ```java Test1: failed ->
skipped Test1: failed -> skipped Test1: failed Test2: failed Test2: failed ``` 至於這裡的 Test2 為什麼會跑 2 次,沒搞明白,正常來說應該是跑一次的,因為程式中 `if(currentRetryCount <= maxRetryCount)` ` 滿足才會重跑,但是這裡是 false 所以不會重跑才對。如果有大神們知道為什麼,希望不吝賜教,留個言。。。 ## 問題解決方案 要解決上述的問題,需要在每組引數化用例結束(無論成功,失敗)後,重置 `currentRetryCount` 的值,讓當前的次數保持在初始化的狀態。 **在實現類 TestngRetry 中加上一個重置的方法,如下:** ```java 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` 方法:** ```java 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
``` **再次執行的結果:** ![](https://javage.github.io/images/blog/retry-2.png) 以上就是對於解決使用了`dataProvider`用例中的每一個引數化用例,在不重置的情況下,用例重跑次數共用的問題。 最後我們加上重置操作後,失敗的用例都會重跑跑 2 次,無論最後成功還是失敗,都會重置 `TestngRetry` 中的`currentRetryCount` 以保證下一個引數化用例開始時,`currentRetryCount` 為初始狀態。 > 參考文章:https://ntflc.com/2018/10/18/TestNg-Retry-Failed-Tests-with-DataP