測試 Spring Boot 應用程式的主類
1. 概述
測試 Spring Boot 應用程式的主類別對於確保應用程式正確啟動至關重要。雖然單元測試通常側重於單一元件,但驗證應用程式上下文載入是否沒有問題可以防止生產中出現執行時間錯誤。
在本教程中,我們將探索有效測試 Spring Boot 應用程式主類別的不同策略。
2. 設定
首先,我們設定一個簡單的 Spring Boot 應用程式。我們可以使用Spring Initializr來產生基本的專案結構。
2.1. Maven 依賴項
為了建立我們的項目,我們需要以下依賴項:
- Spring Boot Starter :建立 Spring Boot 應用程式的核心相依性。
- Spring Boot Starter Test :為 Spring Boot 應用程式提供 JUnit 和 AssertJ 等測試庫。
- Mockito Core :一個模擬框架,用於在單元測試中建立和驗證模擬。
我們將以下依賴項新增到pom.xml
檔案中:
<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);
}
}
關鍵要素是:
@SpringBootApplication
註解:此註解是組合三個基本註解的簡寫:@Configuration:
將此類標記為 bean 定義的來源。@EnableAutoConfiguration:
告訴Spring Boot開始根據classpath
設定、其他bean和屬性設定添加bean。@ComponentScan:
掃描該類別所在的套件並註冊所有Spring元件(bean、服務等)。此組合可確保應用程式配置正確,無需大量 XML 配置或手動設定。
main()
方法:與任何 Java 程式一樣,main()
方法充當入口點。在這裡,它呼叫SpringApplication.run()
,其中:- 引導應用程式並載入 Spring 上下文,根據
@SpringBootApplication
註解配置所有內容。 - 如果是 Web 應用程序,則啟動嵌入式 Web 伺服器(例如 Tomcat 或 Jetty),這意味著該應用程式可以獨立運行,無需外部伺服器。
- 接受命令列參數,可用於在執行時間配置設定檔(如
–spring.profiles.active=dev
)或其他設定。
- 引導應用程式並載入 Spring 上下文,根據
**SpringApplication.run()**
:此方法執行啟動應用程式的繁重工作:- 它會建立一個包含 bean 和配置的
ApplicationContext
,允許 Spring 管理所有依賴項和元件。 - 應用程式屬性或命令列參數中的任何運行時配置都會在此處應用,並啟動依賴這些設定的任何元件。
- 它會建立一個包含 bean 和配置的
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
添加了自訂邏輯(例如,日誌記錄、處理參數或設定自訂屬性),那麼在不載入整個應用程式上下文的情況下測試該邏輯可能是有意義的。在這種情況下,我們可以模擬SpringApplication.run()
來驗證圍繞呼叫的其他行為。
從版本 3.4.0 開始,Mockito 支援靜態方法模擬,這允許我們模擬SpringApplication.run()
。如果我們的main()
方法包含我們想要在不載入完整應用程式上下文的情況下驗證的附加邏輯,則模擬SpringApplication.run()
會特別有用。
為了促進測試隔離,我們可以重構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.從覆蓋範圍中排除主類
如果主類別不包含關鍵邏輯,我們可能會選擇將其從程式碼覆蓋率報告中排除,以專注於更有意義的領域。
要從程式碼覆蓋率中排除main()
方法,我們可以使用@Generated
對其進行註釋,該註釋可從javax.annotation
(如果使用 Jakarta EE,則為jakarta.annotation
)套件中取得。此方法向程式碼覆蓋率工具(例如 JaCoCo 或 SonarQube)發出訊號,表明該方法應在覆蓋率指標中被忽略:
@SpringBootApplication
public class Application {
@Generated(value = "Spring Boot")
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Generated
中的 value 屬性是必需的,它通常指示產生程式碼的來源。在這裡,指定“Spring Boot”
表示程式碼是 Spring Boot 啟動序列的一部分。
如果我們喜歡更簡單的方法並且我們的覆蓋工具支援它,我們也可以在main()
方法上使用@SuppressWarnings(“unused”)
將其從覆蓋範圍中排除:
@SpringBootApplication
public class Application {
@SuppressWarnings("unused")
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
一般來說,使用@Generated
是排除程式碼覆蓋率的更可靠方法,因為大多數工具都會將此註解識別為忽略覆蓋率指標中帶有註解的程式碼的指令。
從覆蓋範圍中排除主類別的另一個選項是直接配置我們的程式碼覆蓋工具。
對於pom.xml
中的JaCoCo
:
<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
從覆蓋率中排除瑣碎的程式碼比僅僅為了滿足覆蓋率指標而編寫測試更實際。
3.6.處理應用程式參數
如果我們的應用程式使用命令列參數,我們可能需要使用特定輸入來測試main()
方法:
public class ApplicationArgumentsTest {
@Test
public void testMainWithArguments() {
String[] args = { "--spring.profiles.active=test" };
Application.main(args);
}
}
此測試檢查應用程式是否使用特定參數正確啟動。當需要驗證某些設定檔或配置時,它非常有用。
4. 結論
測試 Spring Boot 應用程式的主類別可確保應用程式正確啟動並提高程式碼覆蓋率。我們探索了各種策略,從基本的上下文載入測試到模擬SpringApplication.run()
。根據專案的需求,我們可以選擇最能平衡測試執行時間和覆蓋率要求的方法。當方法包含簡單程式碼時,從覆蓋範圍中排除主類別也是一個可行的選擇。
透過實施這些測試,我們增強了應用程式啟動過程的可靠性,並在開發週期的早期發現潛在問題。
本教學的完整原始碼可在 GitHub 上取得。