如何實作 JUnit 測試的重試
1.概述
自動化測試是現代軟體開發的重要組成部分。然而,並非所有測試失敗都是由錯誤引起的。有些失敗是隨機的,由於競爭條件、網路延遲或資源限制等原因而間歇性地發生。
為了應對此類瞬態故障,在測試中實作重試邏輯對我們來說是一個強大的工具。重試機制允許我們的測試在被視為失敗之前重新執行指定的次數,這有助於穩定我們的測試運行並減少 CI 管線中的誤報。
在本教程中,我們將探討如何在 JUnit 4 和 JUnit 5 中實作重試邏輯,實作自訂和基於函式庫的方法,並提供最佳實務指導。
2. 在 JUnit 5 中實作重試邏輯
JUnit 5 引入了強大的擴充模型,與 JUnit 4 相比,它使我們更容易自訂測試行為。在 JUnit 5 中,我們可以透過兩種常用方法實現重試邏輯:建立自訂擴展,在測試失敗時以程式設計方式處理重試,以及使用提供內建重試註解的外部程式庫(如 JUnit Pioneer)。
讓我們探討一下這兩種方法。
2.1. 使用TestExecutionExceptionHandler
自訂重試擴展
我們可以透過實作TestExecutionExceptionHandler
介面在 JUnit 5 中建立自訂擴展,允許在程式碼測試執行期間拋出例外狀況時重試。
以下是使用TestExecutionExceptionHandler
實作的自訂 JUnit 5 擴充功能:
public class RetryExtension implements TestExecutionExceptionHandler {
private static final int MAX_RETRIES = 3;
private static final ExtensionContext.Namespace NAMESPACE =
ExtensionContext.Namespace.create("RetryExtension");
@Override
public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
Store store = context.getStore(NAMESPACE);
int retries = store.getOrDefault("retries", Integer.class, 0);
if (retries < MAX_RETRIES) {
retries++;
store.put("retries", retries);
System.out.println("Retrying test " + context.getDisplayName() + ", attempt " + retries);
throw throwable;
} else {
throw throwable;
}
}
}
為了在實作中使用這個重試機制,讓我們用@ExtendWith(RetryExtension.class)
註解我們的測試類,並編寫我們的測試邏輯,如下所示:
@ExtendWith(RetryExtension.class)
public class RetryTest {
private static int attempt = 0;
@Test
public void testWithRetry() {
attempt++;
System.out.println("Test attempt: " + attempt);
if (attempt < 3) {
throw new RuntimeException("Failing test");
}
}
}
在此實作中,我們使用 JUnit 5 的TestExecutionExceptionHandler
建立一個自訂擴展,用於在測試失敗時重試指定次數(此處我們指定 3 次)。重試次數使用 JUnit 的ExtensionContext.Store
儲存在測試上下文中,並且每次重試都會被記錄下來以方便調試。如果測試在所有嘗試後仍然失敗,則會重新拋出異常以將其標記為失敗。
2.2. 使用 JUnit Pioneer 的@RetryingTest
為了提供更簡單、開箱即用的解決方案,JUnit Pioneer 函式庫為我們提供了@RetryingTest
註釋,可以自動重試失敗的測試。
首先,讓我們將junit-pioneer
依賴項加入到我們的pom.xml
中:
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>2.0.1</version>
<scope>test</scope>
</dependency>
然後,讓我們看看如何在測試中使用它:
public class RetryPioneerTest {
private static int attempt = 0;
@RetryingTest(maxAttempts = 3)
void testWithRetry() {
attempt++;
System.out.println("Test attempt: " + attempt);
if (attempt < 3) {
throw new RuntimeException("Failing test");
}
}
}
JUnit Pioneer 庫中的@RetryingTest
提供了一種無需編寫自訂程式碼即可輕鬆添加重試邏輯的方法。我們只需指定最大嘗試次數,該程式庫就會負責重新執行測試,直到測試通過或達到限制。
3. 在 JUnit 4 中實作重試邏輯
JUnit 4 缺少 JUnit 5 的現代擴充模型,但我們可以透過建立自訂TestRule
來實現重試邏輯。
讓我們看看這個的實作:
public class RetryRule implements TestRule {
private final int retryCount;
public RetryRule(int retryCount) {
this.retryCount = retryCount;
}
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
Throwable failure = null;
for (int i = 0; i < retryCount; i++) {
try {
base.evaluate();
return;
} catch (Throwable t) {
failure = t;
System.out.println("Retry " + (i + 1) + "/" + retryCount +
" for test " + description.getDisplayName());
}
}
throw failure;
}
};
}
}
以下是我們如何使用該規則:
public class RetryRuleTest {
@Rule
public RetryRule retryRule = new RetryRule(3);
private static int attempt = 0;
@Test
public void testWithRetry() {
attempt++;
System.out.println("Test attempt: " + attempt);
if (attempt < 3) {
throw new RuntimeException("Failing test");
}
}
}
在 JUnit 4 中,我們使用名為RetryRule
的自訂TestRule
規則來實作重試邏輯。此規則封裝了測試執行,並在失敗時按配置的次數重試。每次重試都會被記錄下來,如果所有重試都失敗,則拋出最後一次遇到的異常,並將測試標記為失敗。
4. 測試重試的最佳實踐
讓我們回顧一下使用重試的一些最佳實踐:
- 如果測試因時間或環境問題而間歇性失敗,重試是合理的。但對於持續失敗的情況,重試只會掩蓋真正的問題。
- 始終記錄每次重試嘗試以便於調試。
- 重試次數過多會減慢測試執行速度並掩蓋問題。常見的預設重試次數是兩到三次。
- 使用重試作為臨時解決方案並旨在解決根本原因。
5. 結論
重試失敗的測試可以顯著提高測試套件的可靠性,尤其是在偶爾出現不穩定情況的 CI 環境中。 JUnit 4 和 JUnit 5 都支援重試,可以透過自訂邏輯或 JUnit Pioneer 等第三方程式庫來實現。請負責任地使用重試——並非為了忽略錯誤,而是為了在持續建立高品質軟體的同時管理外部不穩定情況。與往常一樣,本文中使用的程式碼可在 GitHub 上取得。