TestNG 中條件性忽略測試
1. 概述
在實際自動化過程中,我們經常需要根據運行時條件(例如環境(測試或生產)、功能標誌、配置、CI/CD 參數等)啟用或停用測試。
在本教學中,我們將介紹在 TestNG 中有條件地跳過測試的不同方法。
2. 理解問題
在TestNG中,我們可以使用參數「 enabled 」且布林值為false @Test來停用測試:
@Test(enabled=false)
public void givenNumbers_sumEquals_thenCorrect() {
int sum = numbers.stream().reduce(0, Integer::sum);
Assert.assertEquals(6, sum);
}
上述程式碼之所以能運行,是因為 Java 註解是在編譯時而不是運行時進行評估的。這意味著下面的程式碼在 Java 中是無效的:
@Test(enabled = isEnabled())
public void givenNumbers_sumEquals_thenCorrect() {
}
TestNG 無法評估註解內部的運行時條件。這就是它不允許使用方法呼叫、表達式和變數的原因。我們可以透過使用一種機制來解決這個問題,該機制在運行時、測試發現或執行期間評估條件。
3. 拋出SkipException異常
TestNG 提供了一個內建的SkipException ,讓我們以程式方式跳過測試。拋出該異常時,TestNG 會將測試標記為已跳過(SKIPPED),而不是失敗(FAILED)。我們可以利用這一點,根據運行時條件以程式方式跳過測試。
我們可以在測試程式碼的不同位置拋出SkipException 。機制不變,只是位置不同。讓我們來探討這兩種方法:
3.1. 跳過測試方法內部
在這種方法中,TestNG 像往常一樣發現並呼叫測試方法。在測試內部,我們在運行時檢查條件。如果條件滿足,則拋出SkipException ,告訴 TestNG 跳過剩餘的測試:
@Test
public void givenConditions_whenUsingSkipException_thenSkipTest() {
if (shouldSkipTest()) {
throw new SkipException("Skipping test for Demonstration!");
}
System.out.println("This line won't get printed");
}
public boolean shouldSkipTest() {
return true;
}
執行上述測試會傳回以下輸出:
Test ignored.
===============================================
Default Suite
Total tests run: 1, Passes: 0, Failures: 0, Skips: 1
===============================================
這裡,我們將跳過測試的邏輯放在了測試方法內部。這會降低程式碼的可讀性。如果我們需要跳過多個測試,就必須在每個測試中重複編寫相同的邏輯。與其在每個測試中都檢查條件,不如將跳過測試的邏輯集中在生命週期鉤子中。
3.2. 跳過使用@BeforeMethod
TestNG 總是在執行測試之前執行諸如@BeforeMethod和@BeforeClass之類的設定方法。當我們在這些方法中拋出SkipException時,TestNG 會將其視為有意跳過。它會立即中止測試並將其標記為已跳過。測試方法永遠不會運作:
public class SkipExceptionInSetupUnitTest {
@BeforeMethod
public void givenConditions_whenUsingSkipException_thenSkipTest() {
if (shouldSkipTest()) {
throw new SkipException("Skipping tests for UK region");
}
}
public boolean shouldSkipTest() {
return true;
}
@Test
public void givenSkipConditionInSetup_whenUsingSkipException_thenSkipTest() {
System.out.println("Dummy Test!");
}
@Test
public void givenSkipConditionInSetup_whenUsingSkipException_thenSkipTest2() {
System.out.println("Dummy Test 2!");
}
}
執行上述測試將傳回以下輸出:
Test ignored.
Test ignored.
===============================================
Default Suite
Total tests run: 2, Passes: 0, Failures: 0, Skips: 2
Configuration Failures: 0, Skips: 2
===============================================
這種方法會跳過類別中的所有測試,因此控製粒度較差。當多個測試共享相同的運行時條件時,我們應該使用這種方法。它有助於避免在每個測試中重複編寫跳過邏輯。
4. 實作IAnnotationTransformer
IAnnotationTransformer是 TestNG 的一個監聽器接口,允許我們在執行時修改測試註解。 TestNG 會在處理每個@Test註解之前呼叫其transform()方法。我們可以動態地更改諸如enabled, priority,或groups之類的屬性,而無需修改原始程式碼。這對於根據運行時條件啟用或停用測試非常有用:
public class SkipAnnotationTransformer implements IAnnotationTransformer {
@Override
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
if (testMethod != null && isTargetTestClass(testMethod) && shouldSkipTest()) {
annotation.setEnabled(false);
}
}
private boolean isTargetTestClass(Method testMethod) {
return testMethod.getDeclaringClass().equals(SkipAnnotationTransformerUnitTest.class);
}
public boolean shouldSkipTest() {
return true;
}
}
在transform()方法中,我們會檢查測試方法是否屬於SkipAnnotationTransformerUnitTest ,以及shouldSkipTest()是否回傳 true。如果兩個條件都滿足,則設定annotation.setEnabled(false).這將徹底停用該測試——TestNG 不會調度該測試,不會運行配置方法,也不會將其計入測試計數。
在testng.xml檔案中,我們使用<listeners>標籤將SkipAnnotationTransformer註冊為監聽器。我們定義了一個測試套件,該套件會掃描所有符合com.baeldung.testng.*包,以自動發現並執行測試類別:
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Test Suite">
<listeners>
<listener class-name="com.baeldung.testng.conditionalskip.SkipAnnotationTransformer"/>
</listeners>
<test name="All Tests">
<packages>
<package name="com.baeldung.testng.*"/>
</packages>
</test>
</suite>
我們配置 Maven Surefire 外掛程式來執行我們的 TestNG 測試。我們新增了surefire-testng依賴項,強制 Surefire 使用 TestNG 提供程序,而不是自動偵測 JUnit。我們使用<suiteXmlFiles>將 Surefire 指向我們的testng.xml文件,以便它使用我們的測試套件配置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-testng</artifactId>
<version>3.2.5</version>
</dependency>
</dependencies>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
</suiteXmlFiles>
</configuration>
</plugin>
我們在SkipAnnotationTransformerUnitTest,我們將執行該測試案例來驗證轉換器的行為:
public class SkipAnnotationTransformerUnitTest {
@Test
public void givenSkipAnnotation_whenUsingIAnnotationTransformer_thenSkipTest() {
System.out.println("Dummy Test!");
}
@Test
public void givenSkipAnnotation_whenUsingIAnnotationTransformer_thenSkipTest2() {
System.out.println("Dummy Test 2!");
}
}
為了只執行SkipAnnotationTransformerUnitTest ,我們建立了一個名為testng-transformer-demo.xml的示範檔:
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Transformer Demo Suite">
<listeners>
<listener class-name="com.baeldung.testng.conditionalskip.SkipAnnotationTransformer"/>
</listeners>
<test name="Transformer Demo">
<classes>
<class name="com.baeldung.testng.conditionalskip.SkipAnnotationTransformerUnitTest"/>
</classes>
</test>
</suite>
現在,我們將使用以下命令運行測試:
mvn test -Dsurefire.suiteXmlFiles=src/test/resource/testng-transformer-demo.xml
我們沒有看到SkipAnnotationTransformerUnitTest中定義的測試處於執行或跳過狀態。 TestNG 完全忽略了它們。
5. 結論
本文討論了在 TestNG 中有條件地忽略測試的各種方法。
我們可以使用SkipException來採取最簡單、最明確的方法。但是,這適用於測試執行期間的跳過決策,而不是測試發現期間的跳過決策。其主要優勢在於可見性,因為跳過的測試會被明確地報告為「已跳過」。
或者,我們可以使用IAnnotationTransformer ,它在測試發現階段更早發揮作用。我們會停用那些在執行開始前就不符合條件的測試,這樣它們就不會運行,也不會被標記為已跳過。這可以帶來更清晰的計數和更快的執行速度。
選擇哪種方式取決於我們需要的是報告透明度還是執行過程的清晰性。
和往常一樣,程式碼可以在 GitHub 上找到。