模擬 Logger 和 LoggerFactory
1.概述
日誌記錄是軟體開發中不可或缺的一部分。我們經常添加日誌來簡化調試並深入了解應用程式的內部行為。
在編寫測試時,我們可能不想依賴真實的日誌記錄基礎架構。 Mocking 允許我們透過用可控的替代品取代被測類的依賴項來隔離被測類。
在本教學中,我們將探討如何使用 Mockito 來模擬Logger
並與LoggerFactory
搭配使用。此外,我們還將透過範例逐步演示該過程。
2. Logger
和LoggerFactory
類
SLF4J 庫提供了Logger
接口,該接口提供了常見的日誌記錄方法,例如info()
、 error()
和debug().
此外,該程式庫還提供了LoggerFactory
類,該類別可協助為給定類別建立記錄器實例。
通常,Java 應用程式中的記錄器會宣告為靜態欄位:
private static final Logger logger = LoggerFactory.getLogger(Example.class);
由於Loggerfactory.getLogger()
是靜態方法,因此模擬可能需要第三方工具(例如 PowerMock)。但是,從 Mockito 3.4.0 開始,我們現在可以使用MockedStatic
類別直接模擬靜態方法,從而無需使用 PowerMock 或專用的測試運行器。
3. Maven依賴項
為了示範如何模擬Logger
和Loggerfactory
,讓我們啟動一個簡單的 Java 專案並將mockito-core
依賴項新增到我們的pom.xml
中:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.19.0</version>
<scope>test</scope>
</dependency>
mockito-core
依賴項提供了用於建立模擬和存根的核心類別。
另外,讓我們將slf4j-api
和junit-jupiter-api
依賴項加入pom.xml
中:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.17</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
此外, slf4j-api
使我們能夠登入控制台,而junit-jupiter-api
有助於編寫 JUnit 5 測試。
4. 服務等級
接下來,讓我們寫一個服務類別並新增一個要測試的方法:
class UserService {
private final Logger logger;
public UserService(Logger logger) {
this.logger = logger;
}
public void checkAdminStatus(boolean isAdmin) {
if (isAdmin) {
logger.info("You are an admin, access granted");
} else {
logger.error("You are not an admin");
}
}
public void processUser(String username) {
logger.info("Processing user: {}", username);
logger.warn("Please don't close your browser ...");
logger.info("Processing complete");
}
}
在上面的程式碼中,我們定義了一個名為UserService
類,它包含兩個方法。第一個方法名為checkAdminStatus(),
用來檢查使用者是否為管理員。第二個方法名為processUser()
,用於透過在控制台上記錄某些資訊來處理使用者。
此外,我們透過建構函數注入Logger
實例,而不是直接實例化它。這種設計使類別更易於測試,因為我們可以在測試時提供一個模擬的Logger
。
5.單元測試
接下來,讓我們為UserService
類別編寫單元測試。首先,讓我們定義一個單元測試類別並設定必要的模擬:
class UserServiceUnitTest {
private Logger mockLogger;
private UserService userService;
@BeforeEach
public void setup() {
mockLogger = mock(Logger.class);
userService = new UserService(mockLogger);
}
}
這裡,我們建立了一個mockLogger
並將其註入到UserService
實例中。這樣可以在測試過程中輕鬆驗證日誌記錄行為。
接下來,讓我們來寫第一個測試案例,其中使用者不是管理員:
@Test
void givenUserServiceLogic_whenVerifyingIfUserIsNotAnAdmin_thenReturnCorrectLog() {
try (MockedStatic<LoggerFactory> mockedFactory = mockStatic(LoggerFactory.class)) {
mockedFactory.when(() -> LoggerFactory.getLogger(UserService.class))
.thenReturn(mockLogger);
userService.checkAdminStatus(false);
verify(mockLogger).error("You are not an admin");
}
}
在上面的程式碼中,我們使用MockedStatic
類別來攔截對LoggerFactory.getLogger()
的呼叫並傳回我們的模擬Logger
。然後,我們呼叫被測方法並驗證是否記錄了預期的錯誤訊息。
接下來,讓我們為使用者是管理員的情況再增加一個測試:
@Test
void givenUserServiceLogic_whenVerifyingIfUserIsAnAdmin_thenReturnCorrectLog() {
try (MockedStatic<LoggerFactory> mockedFactory = mockStatic(LoggerFactory.class)) {
mockedFactory.when(() -> LoggerFactory.getLogger(UserService.class))
.thenReturn(mockLogger);
userService.checkAdminStatus(true);
verify(mockLogger).info("You are an admin, access granted");
}
}
在這裡,我們檢查肯定的情況並驗證是否產生了正確的info
日誌訊息。
最後,讓我們寫一個測試來驗證多個日誌呼叫:
@Test
void givenUserServiceLogic_whenProcessingAUser_thenLogMultipleMessage() {
try (MockedStatic<LoggerFactory> mockedFactory = mockStatic(LoggerFactory.class)) {
mockedFactory.when(() -> LoggerFactory.getLogger(UserService.class))
.thenReturn(mockLogger);
userService.processUser("Harry");
InOrder inOrder = inOrder(mockLogger);
inOrder.verify(mockLogger).info("Processing user: {}", "Harry");
inOrder.verify(mockLogger).info("Processing complete");
}
}
在上面的程式碼中,我們測試了processUser()
方法,該方法會進行兩次日誌記錄呼叫。此外,我們呼叫verify()
方法兩次——每個預期的日誌訊息調用一次,並使用inOrder()
方法來確保訊息按正確的順序記錄。這保證了兩個訊息都按預期準確捕獲。
6. 結論
在本文中,我們學習如何使用 Mockito 模擬Logger
和LoggerFactory
,而無需依賴 PowerMock 之類的運行器。此外,我們也了解如何使用MockedStatic
類別來模擬靜態方法,從而可以在測試期間攔截對LoggerFactory.getLogger()
的呼叫。
與往常一樣,範例的原始程式碼可在 GitHub 上取得。