1. 概述
測試 Spring Boot 應用程序的主類至關重要,以確保應用程序能夠正確啓動。單元測試通常側重於單個組件,但驗證應用程序上下文是否在沒有問題的情況下加載,可以防止生產環境中出現運行時錯誤。
在本教程中,我們將探索各種有效測試 Spring Boot 應用程序主類的策略。
2. 搭建環境
為了開始,我們將搭建一個簡單的 Spring Boot 應用。我們可以使用 Spring Initializr 生成基本的項目結構。
2.1. Maven 依賴
為了設置我們的項目,我們需要以下依賴項:
- Spring Boot Starter:構建 Spring Boot 應用的核心依賴。
- Spring Boot Starter Test:提供 JUnit 和 AssertJ 等測試庫,用於 Spring Boot 應用。
- Mockito Core:用於單元測試中創建和驗證 mock 對象的 mocking 框架。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.0.0</version>
<scope>test</scope>
</dependency>2.2. 主應用程序類
主應用程序類是任何 Spring Boot 應用程序的核心。它不僅作為應用程序的入口點,還作為主要的配置類,管理組件並設置環境。下面我們將深入瞭解其結構及其每個部分的重要性。
以下是一個典型的 Spring Boot 主類示例:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}關鍵要素如下:
- 註解:此註解是三個基本註解的簡寫:
這種組合確保應用程序在無需進行大量的 XML 配置或手動設置的情況下就能正確配置。
-
- 方法:就像任何 Java 程序一樣,
- 應用程序並加載 Spring 容器,根據
- 內嵌 Web 服務器(如 Tomcat 或 Jetty),如果它是 Web 應用程序,這意味着應用程序可以在無需外部服務器的情況下獨立運行。
- 命令列參數,可用於在運行時配置 Profile(如
- 它創建一個
- 在此處應用運行時配置(來自應用程序屬性或命令行參數),並啓動任何依賴於這些設置的組件。
- 應用程序並加載 Spring 容器,根據
2.3. 自定義和測試主應用程序類
對於大多數應用程序,application.properties 或 application.yml 是首選配置設置的地方,因為它能保持主類簡潔,並將設置集中在一個文件中。但是,我們也可以在主類中直接自定義某些設置:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.setBannerMode(Banner.Mode.OFF);
app.setLogStartupInfo(false);
app.setDefaultProperties(Collections.singletonMap("server.port", "8083"));
app.run(args);
}
}在本示例中,我們正在實施以下調整:
- 禁用橫幅: Spring Boot 橫幅默認在啓動時打印。為了獲得更乾淨的控制枱輸出,我們可以禁用它。
- 抑制啓動日誌: Spring Boot 默認會記錄大量的初始化信息。如果不需要,我們可以關閉一些日誌。
- 設置默認屬性: 我們可以添加默認屬性,例如指定自定義服務器端口。
這些小調整在測試環境中或調試時尤其有用,此時我們希望控制應用程序的詳細程度和行為。
測試主應用程序類有時可能顯得多餘,但它是必不可少的,因為它驗證:
- 上下文加載: 確保所有必需的 Bean 和配置都已到位。
- 環境配置: 驗證運行時配置文件和環境屬性是否已正確應用。
- 啓動邏輯: 確認主類中添加的任何自定義邏輯(例如事件監聽器或橫幅)不會導致啓動問題。
通過充分理解和自定義我們的主應用程序類,我們可以確保我們的 Spring Boot 應用程序在不同的環境中具有靈活性,無論是在開發、測試還是生產環境中。
3. 測試策略
我們將探討多種測試主類的策略,從基本的上下文加載測試到模擬和命令行參數。
3.1. 基本上下文加載測試
要測試應用程序上下文是否加載,最簡單的方法是使用 @SpringBootTest,無需添加任何其他參數:
@SpringBootTest
public class ApplicationContextTest {
@Test
void contextLoads() {
}
}這裏,@SpringBootTest 加載完整的應用程序上下文。如果任何 Bean 配置不正確,則測試會失敗,這有助於我們儘早發現問題。在大型應用程序中,我們可能只考慮配置此測試以加載特定的 Bean,從而加快執行速度。
3.2. 直接測試 main() 方法
為了針對像 SonarQube 這樣的工具覆蓋 main() 方法,我們可以直接對其進行測試:
public class ApplicationMainTest {
@Test
public void testMain() {
Application.main(new String[]{});
}
}這個簡單的測試驗證 main() 方法在不拋出異常的情況下執行。它不加載整個上下文,但確保方法在運行時沒有問題。
3.3. 模擬 SpringApplication.run()
啓動整個應用程序上下文耗時,為了優化這一點,我們可以使用 Mockito 模擬 SpringApplication.run()。
如果在我們的 main() 方法中添加了自定義邏輯(例如,日誌記錄、處理參數或設置自定義屬性),那麼在不加載整個應用程序上下文的情況下測試這些邏輯可能是有意義的。在這種情況下,我們可以模擬 SpringApplication.run() 以驗證圍繞調用添加的行為。
從 3.4.0 版本開始,Mockito 支持靜態方法模擬,這允許我們模擬 SpringApplication.run()。 模擬 SpringApplication.run() 在我們的 main() 方法包含我們想要驗證的額外邏輯,而無需加載完整的應用程序上下文時尤其有用。
為了促進測試隔離,我們可以重構 main() 方法,以便實際的啓動邏輯在可測試的單獨方法中處理。 這種分離允許我們專注於測試初始化邏輯,而無需啓動整個應用程序:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
initializeApplication(args);
}
static ConfigurableApplicationContext initializeApplication(String[] args) {
return SpringApplication.run(Application.class, args);
}
}現在,main() 方法委託給 initializeApplication(),我們可以對其進行隔離測試。
經過重構的 initializeApplication() 方法後,我們可以繼續模擬 SpringApplication.run() 並驗證其行為,而無需完全啓動應用程序上下文:
public class ApplicationMockTest {
@Test
public void testMainWithMock() {
try (MockedStatic<SpringApplication> springApplicationMock = mockStatic(SpringApplication.class)) {
ConfigurableApplicationContext mockContext = mock(ConfigurableApplicationContext.class);
springApplicationMock.when(() -> SpringApplication.run(Application.class, new String[] {}))
.thenReturn(mockContext);
Application.main(new String[] {});
springApplicationMock.verify(() -> SpringApplication.run(Application.class, new String[] {}));
}
}
}在本次測試中,我們正在模擬 SpringApplication.run() 以防止實際應用程序啓動,從而節省時間並隔離測試。通過返回一個模擬的 ConfigurableApplicationContext,我們能夠安全地處理 initializeApplication() 中的所有交互,避免真實的上下文初始化。此外,我們使用 verify() 來確認 SpringApplication.run() 被正確調用並帶有正確的參數,這允許我們在不要求完整的應用程序上下文的情況下驗證啓動序列。
這種方法尤其適用於 main() 方法包含自定義啓動邏輯的情況,因為它允許我們獨立測試和驗證該邏輯,保持測試執行速度和隔離性。
3.4. 使用 @SpringBootTest 與 useMainMethod
從 Spring Boot 2.2 版本開始,我們可以指示 @SpringBootTest 在啓動應用程序上下文時使用 main() 方法:
@SpringBootTest(useMainMethod = SpringBootTest.UseMainMethod.ALWAYS)
public class ApplicationUseMainTest {
@Test
public void contextLoads() {
}
}將useMainMethod 設置為 ALWAYS 確保 main() 方法在測試期間運行。這種方法在以下場景中可能很有用:
- main() 方法包含額外的設置邏輯:如果我們的 main() 方法包含任何對應用程序正確啓動(例如,設置自定義屬性或附加日誌記錄)的重要設置或配置,則此測試將作為上下文初始化的一部分驗證該邏輯。
- 代碼覆蓋率提高: 這種策略允許我們在測試中覆蓋 main() 方法,從而確保完整的啓動序列,包括 main() 方法,在一個測試中得到驗證。這在希望在不編寫單獨測試來直接調用 main() 方法的情況下實現完整的啓動驗證時尤其有用。
3.5. 從主類中排除覆蓋
如果主類不包含關鍵邏輯,我們可能會選擇將其從代碼覆蓋報告中排除,以便專注於更有意義的區域。
要從代碼覆蓋中排除 <em >main()</em > 方法,我們可以使用 <em >@Generated</em > 註解對其進行標記,該註解來自 `javax.annotation (或 jakarta.annotation 如果使用 Jakarta EE) 包。 這種方法會向代碼覆蓋工具(如 JaCoCo 或 SonarQube)發出信號,指示該方法不應在覆蓋指標中進行計算。
@SpringBootApplication
public class Application {
@Generated(value = "Spring Boot")
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}“Spring Boot” 屬性值在 @Generated 中是必需的,通常表示生成的代碼的來源。在這裏,指定 “Spring Boot” 可以明確指出此代碼是 Spring Boot 啓動序列的一部分。
如果更傾向於簡化方法,並且我們的覆蓋工具支持,我們也可以使用 @SuppressWarnings(“unused”) 在 main() 方法上進行標記,從而將其排除在覆蓋範圍之外。
@SpringBootApplication
public class Application {
@SuppressWarnings("unused")
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}一般來説,使用 @Generated 是一種更可靠的代碼覆蓋排除方法,因為大多數工具都將其識別為指示在覆蓋度量中忽略標註的代碼的指令。
另一個排除主類從覆蓋度量中的方法是直接配置我們的代碼覆蓋工具。
對於 JaCoCo 在 pom.xml 中:
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<configuration>
<excludes>
<exclude>com/baeldung/mainclasstest/Application*</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>對於 SonarQube (在 sonar-project.properties 中):
sonar.exclusions=src/main/java/com/baeldung/mainclasstest/Application.java從覆蓋面中排除 trivial 代碼可以比僅僅為了滿足覆蓋率指標而編寫測試更實際。
3.6. 處理應用程序參數
如果我們的應用程序使用命令行參數,我們可能希望通過提供特定輸入來測試 main() 方法:
public class ApplicationArgumentsTest {
@Test
public void testMainWithArguments() {
String[] args = { "--spring.profiles.active=test" };
Application.main(args);
}
}此測試用於檢查應用程序在特定參數下是否正確啓動。它在驗證特定配置文件或配置時非常有用。
4. 結論
測試 Spring Boot 應用程序的主類可確保應用程序正確啓動,並提高代碼覆蓋率。我們探討了各種策略,從基本的上下文加載測試到模擬 SpringApplication.run()。 根據項目的需求,我們可以選擇在測試執行時間和覆蓋率要求之間取得最佳平衡的方法。 從覆蓋率中排除主類也是當方法包含 trivial 代碼時一種可行的選擇。
通過實施這些測試,我們增強了應用程序啓動過程的可靠性,並在開發週期早期捕獲潛在問題。