如何列印 JUnit 斷言結果
1. 概述
測試中的標準做法是依賴測試案例的結果,這些結果可能通過也可能失敗。然而,有時我們也需要列印斷言結果。這在以下幾種情況下很有幫助:
- 除錯複雜的測試場景
- 監控 CI/CD 管線中的測試執行情況
- 根據日誌建立詳細的測試報告
在本文中,我們將回顧幾種將斷言結果列印到日誌中的方法。
2. 斷言訊息及斷言後日誌記錄
最直接、最準確的方法是使用精心建構的斷言訊息以及斷言後邏輯。讓我們創建一個… 我們將在測試用例中使用的TestService :
public class TestService {
public boolean successfulCall() {
return true;
}
public boolean failedCall() {
return false;
}
public boolean exceptionCall() {
throw new RuntimeException("Service error");
}
}
在這個服務中,我們提供了測試通過情況、失敗情況以及驗證異常拋出行為的方法。現在,讓我們來創建 TestServiceTest ,我們將在其中進行驗證 TestService.我們創建的第一個測試案例應該驗證呼叫是否成功:
class TestServiceTest {
private static final Logger logger = LoggerFactory.getLogger(TestServiceTest.class);
TestService service = new TestService();
@Test
void whenSuccessfulCall_thenSuccessMessagePrintedIntoLog() {
logger.info("Testing successful call...");
assertTrue(service.successfulCall(), "Service should return true for successful call");
logger.info("✓ Successful call assertion passed");
}
}
在本測試案例中,我們在斷言前後添加了資訊日誌,以指示斷言通過的過程。如果斷言失敗,我們也會加入清晰的描述資訊。測試通過後,我們可以在日誌中看到以下資訊:
16:21:50.072 [main] INFO com.baeldung.printassertionresults.TestServiceTest -- Testing successful call...
16:21:50.076 [main] INFO com.baeldung.printassertionresults.TestServiceTest -- ✓ Successful call assertion passed
現在,讓我們新增一個會失敗的測試案例:
@Test
void whenFailedCall_thenFailureMessagePrintedIntoLog() {
logger.info("Testing failed call...");
assertTrue(service.failedCall(), "Service should return true for failed call");
}
在這裡,我們在斷言之前添加了日誌記錄,並期望失敗訊息出現在日誌中。
正如預期的那樣,測試案例未通過,以下是日誌輸出:
16:26:14.007 [main] INFO com.baeldung.printassertionresults.TestServiceTest -- Testing failed call...
org.opentest4j.AssertionFailedError: Service should return true for failed call
3. 捕獲斷言異常
另一種方法是捕獲斷言異常,並為通過和失敗的斷言建立自訂日誌記錄。讓我們從創建一個…開始。 我們將使用Assertion介面來封裝我們的基本斷言:
public interface Assertion {
void doAssert() throws AssertionError;
}
接下來,我們來創建… AssertionWithMessage類別:
public class AssertionWithMessage {
private final Assertion assertion;
private final String message;
public AssertionWithMessage(Assertion assertion, String message) {
this.assertion = assertion;
this.message = message;
}
public void doAssert() {
assertion.doAssert();
}
public String getMessage() {
return message;
}
}
在 doAssert()方法中,我們代理程式對真正斷言邏輯的呼叫。此外,我們也會處理斷言期間要記錄的訊息。現在,讓我們來創建… LoggingAssertions類:
public class LoggingAssertions {
private static final Logger logger = LoggerFactory.getLogger(LoggingAssertions.class);
public static void assertAll(AssertionWithMessage... assertions) {
boolean failed = false;
for (AssertionWithMessage assertion : assertions) {
try {
assertion.doAssert();
logger.info("✓ {}", assertion.getMessage());
} catch (AssertionError e) {
failed = true;
logger.error("✗ {} - {}", assertion.getMessage(), e.getMessage());
}
}
if (failed) {
/*
* Critical: Re-throw to maintain test failure behavior
* */
throw new AssertionError("One of the assertions was failed. See logs for details");
}
}
}
在assertAll()方法中,我們會逐一執行每個斷言。如果斷言通過,我們會記錄一條帶有「成功」前綴的訊息。如果發生 ` AssertionError ,我們會記錄相同的斷言訊息,但前綴改為“失敗”,並包含錯誤描述。**如果任何斷言失敗,重新拋出AssertionError至關重要;否則,測試流程將無法正確識別需要修復的測試案例**。
現在,讓我們建立一個測試案例,使用我們的LoggingAssertions類別來驗證所有TestService方法:
@Test
void whenRunMultipleAssertionsWithLogging_thenAllTheLogsShouldBePrintedAndFailureExceptionsRethrown() {
LoggingAssertions.assertAll(
new AssertionWithMessage(
() -> assertTrue(service.successfulCall()),
"Successful call should return true"),
new AssertionWithMessage(
() -> assertTrue(service.failedCall()),
"Failed call should return true"),
new AssertionWithMessage(
() -> assertThrows(RuntimeException.class, service::exceptionCall),
"Exception call should throw RuntimeException")
);
}
這裡,我們一次檢查所有斷言。因此,測試用例將失敗,日誌訊息將顯示斷言結果:
16:44:26.184 [main] INFO com.baeldung.printassertionresults.LoggingAssertions -- ✓ Successful call should return true
16:44:26.187 [main] ERROR com.baeldung.printassertionresults.LoggingAssertions -- ✗ Failed call should return true - expected: <true> but was: <false>
16:44:26.188 [main] INFO com.baeldung.printassertionresults.LoggingAssertions -- ✓ Exception call should throw RuntimeException
java.lang.AssertionError: One of the assertions was failed. See logs for details
4. JUnit 5 測試生命週期處理擴展
我們也可以利用 JUnit 5 擴充功能來記錄測試案例是通過還是失敗。 這為測試斷言日誌記錄提供了一個集中式實作。然而,這種日誌記錄的工作層級高於單一斷言。透過為每個測試案例設計一個斷言,我們仍然可以實現目標。讓我們創建一個… TestResultLogger類別:
public class TestResultLogger implements TestWatcher, BeforeEachCallback {
private static final Logger logger = LoggerFactory.getLogger(TestResultLogger.class);
@Override
public void beforeEach(ExtensionContext context) throws Exception {
logger.info("Testing {}", context.getDisplayName());
}
@Override
public void testSuccessful(ExtensionContext context) {
logger.info("✓ {} assertion passed", context.getDisplayName());
}
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
logger.error("✗ {} assertion didn't pass", context.getDisplayName());
}
}
在這裡,我們實作了TestWatcher ,並在testSuccessful和testFailed方法中加入了日誌邏輯。此外,我們還實作了BeforeEachCallback接口,以便在每次測試之前新增日誌記錄。現在,讓我們開始創建吧。 附加的TestServiceWithTestWatcherTest TestResultLogger擴充:
@ExtendWith(TestResultLogger.class)
class TestServiceWithTestWatcherTest {
TestService service = new TestService();
@Test
void whenSuccessfulCall_thenTrueShouldBeReturned() {
assertTrue(service.successfulCall());
}
@Test
void whenExceptionCall_thenExpectedExceptionShouldBeThrown() {
assertThrows(RuntimeException.class, service::exceptionCall);
}
@Test
void whenFailedCall_thenTrueShouldBeReturned() {
assertTrue(service.failedCall());
}
}
這裡,我們實作了多個測試案例,每個用例都包含一個特定的斷言,以覆蓋TestService所有功能。以下是測試執行後的日誌輸出:
11:52:53.004 [main] INFO com.baeldung.printassertionresults.TestResultLogger -- Testing whenExceptionCall_thenExpectedExceptionShouldBeThrown()
11:52:53.026 [main] INFO com.baeldung.printassertionresults.TestResultLogger -- ✓ whenExceptionCall_thenExpectedExceptionShouldBeThrown() assertion passed
11:52:53.029 [main] INFO com.baeldung.printassertionresults.TestResultLogger -- Testing whenFailedCall_thenTrueShouldBeReturned()
11:52:53.038 [main] ERROR com.baeldung.printassertionresults.TestResultLogger -- ✗ whenFailedCall_thenTrueShouldBeReturned() assertion didn't pass
org.opentest4j.AssertionFailedError:
Expected :true
Actual :false
...
11:52:53.054 [main] INFO com.baeldung.printassertionresults.TestResultLogger -- ✓ whenSuccessfulCall_thenTrueShouldBeReturned() assertion passed
這種方法的優點是,我們可以編寫出簡潔的測試案例程式碼,而無需在測試案例中添加額外的日誌列印邏輯。
5. 結論
本文回顧了多種列印斷言結果的方法。我們可以選擇最適合自己的方法。作為基準,建議使用清晰的斷言訊息,這可以涵蓋大多數情況。對於更複雜的斷言邏輯,我們可以圍繞斷言異常重拋來建立實用程式。為了實現集中化且易於附加的行為,我們可以選擇實作日誌擴充。
代碼一如既往地可用。 在 GitHub 上。