系統存根庫指南

1.概述

當它依賴於系統資源(例如環境變量,系統屬性)或使用進程級操作(例如System.exit時,可能很難測試我們的軟件。

Java沒有提供用於設置環境變量的直接方法,因此我們冒著風險,即在一個測試中設置的值會影響另一個測試的執行。同樣,我們可能會避免為可能執行System.exit代碼編寫JUnit測試,因為它有可能中止測試。

系統規則和系統Lambda庫是這些問題的早期解決方案。在本教程中,我們將研究一個名為System Stubs的System Lambda的新分支,它提供了JUnit 5替代方案。

2.為什麼要使用系統存根?

2.1。系統Lambda不是JUnit插件

原始的系統規則庫僅可用於JUnit4。在JUnit 5下,它仍可與JUnit Vintage一起使用,但這需要繼續創建JUnit 4測試。該庫的創建者產生了一個與系統無關的測試框架版本,稱為System Lambda,該版本旨在在每種測試方法中使用:

@Test

 void aSingleSystemLambda() throws Exception {

 restoreSystemProperties(() -> {

 System.setProperty("log_dir", "test/resources");

 assertEquals("test/resources", System.getProperty("log_dir"));

 });



 // more test code here

 }

測試代碼表示為lambda,傳遞給設置必要的存根的方法。清理將在控制權返回到其餘測試方法之前進行。

儘管此方法在某些情況下效果很好,但該方法有一些缺點。

2.2。避免額外的代碼

系統Lambda方法的好處是,工廠類中有一些通用的配方可以執行特定類型的測試。但是,當我們想在許多測試用例中使用它時,這會導致一些代碼膨脹。

首先,即使測試代碼本身未引發檢查異常,但wrapper方法也會throws Exception 。其次,在多個測試中設置相同的規則需要代碼重複。每個測試都需要獨立執行相同的配置。

但是,這種方法最麻煩的方面是當我們嘗試一次設置多個工具時。假設我們要設置一些環境變量和系統屬性。在我們的測試代碼開始之前,我們最終需要兩個層次的嵌套:

@Test

 void multipleSystemLambdas() throws Exception {

 restoreSystemProperties(() -> {

 withEnvironmentVariable("URL", "https://www.baeldung.com")

 .execute(() -> {

 System.setProperty("log_dir", "test/resources");

 assertEquals("test/resources", System.getProperty("log_dir"));

 assertEquals("https://www.baeldung.com", System.getenv("URL"));

 });

 });

 }

在這裡,JUnit插件或擴展可以幫助我們減少測試中所需的代碼量。

2.3。使用更少的樣板

我們應該期望能夠以最少的樣板編寫測試:

@SystemStub

 private EnvironmentVariables environmentVariables = ...;



 @SystemStub

 private SystemProperties restoreSystemProperties;



 @Test

 void multipleSystemStubs() {

 System.setProperty("log_dir", "test/resources");

 assertEquals("test/resources", System.getProperty("log_dir"));

 assertEquals("https://www.baeldung.com", System.getenv("ADDRESS"));

 }

這種方法是由SystemStubs JUnit 5擴展提供的,它使我們的測試可以用更少的代碼來組成。

2.4。測試生命週期掛鉤

當唯一可用的工具是執行模式時,就不可能將存根行為掛鉤到測試生命週期的所有部分。嘗試將其與其他JUnit擴展(例如@SpringBootTest結合使用時,這尤其具有挑戰性。

如果我們想圍繞Spring Boot測試設置一些環境變量,那麼我們就不可能將整個測試生態系統合理地嵌入到一個測試方法中。我們需要一種方法來激活圍繞測試套件的測試設置。

System Lambda所採用的方法永遠不可能做到這一點,這是創建系統存根的主要原因之一。

2.5。鼓勵動態特性

其他用於設置系統屬性的框架,例如JUnit Pioneer ,則強調了編譯時已知的配置。在可能使用Testcontainers或Wiremock的現代測試中,我們需要在這些工具啟動後基於隨機運行時設置來設置我們的系統屬性。這與可在整個測試生命週期中使用的測試庫一起使用時效果最佳。

2.6。更多可配置性

使用現成的測試配方(例如catchSystemExit是有益的,這些配方可以環繞測試代碼來完成一項工作。但是,這依賴於測試庫開發人員來提供我們可能需要的每種配置選項。

按組成進行配置更靈活,並且是新的系統存根實現的很大一部分。

但是, System Stubs支持System Lambda的原始測試構造,以實現向後兼容。此外,它提供了新的JUnit 5擴展,一組JUnit 4規則以及許多其他配置選項。儘管基於原始代碼,但已對其進行了大量重構和模塊化,以提供更豐富的功能。

讓我們更多地了解它。

3.入門

3.1 依賴關係

JUnit的5擴展需要一個合理高達最新版本的JUnit 5

<dependency>

 <groupId>org.junit.jupiter</groupId>

 <artifactId>junit-jupiter</artifactId>

 <version>5.6.2</version>

 <scope>test</scope>

 </dependency>

讓我們將所有系統存根庫依賴項添加到我們的pom.xml

<!-- for testing with only lambda pattern -->

 <dependency>

 <groupId>uk.org.webcompere</groupId>

 <artifactId>system-stubs-core</artifactId>

 <version>1.1.0</version>

 <scope>test</scope>

 </dependency>



 <!-- for JUnit 4 -->

 <dependency>

 <groupId>uk.org.webcompere</groupId>

 <artifactId>system-stubs-junit4</artifactId>

 <version>1.1.0</version>

 <scope>test</scope>

 </dependency>



 <!-- for JUnit 5 -->

 <dependency>

 <groupId>uk.org.webcompere</groupId>

 <artifactId>system-stubs-jupiter</artifactId>

 <version>1.1.0</version>

 <scope>test</scope>

 </dependency>

我們應該注意,我們只需要導入我們正在使用的測試框架所需的數量就可以了。的確,後兩者在傳遞性上都包含核心依賴性。

現在讓我們編寫第一個測試。

3.2。 JUnit 4環境變量

我們可以通過在類型為EnvironmentVariablesRule測試類中聲明一個帶有JUnit 4 @Rule註釋的字段來控制環境變量。這將在我們的測試運行時由JUnit 4激活,並允許我們在測試中設置環境變量:

@Rule

 public EnvironmentVariablesRule environmentVariablesRule = new EnvironmentVariablesRule();



 @Test

 public void givenEnvironmentCanBeModified_whenSetEnvironment_thenItIsSet() {

 environmentVariablesRule.set("ENV", "value1");



 assertThat(System.getenv("ENV")).isEqualTo("value1");

 }

實際上,我們可能更願意在@Before方法中設置環境變量值,以便可以在所有測試之間共享該設置:

@Before

 public void before() {

 environmentVariablesRule.set("ENV", "value1")

 .set("ENV2", "value2");

 }

在這裡,我們應該注意使用流暢set方法method chaining輕鬆設置多個值成為可能。

我們還可以使用EnvironmentVariablesRule對象的構造函數來提供有關構造的值:

@Rule

 public EnvironmentVariablesRule environmentVariablesRule =

 new EnvironmentVariablesRule("ENV", "value1",

 "ENV2", "value2");

構造函數有多個重載,允許以不同的形式提供變量。 varargs提供任意數量的名稱-值對。

每個系統存根JUnit 4規則都是核心存根對象之一的子類。它們還可以在整個測試類的生命週期中使用, @ClassRulestatic字段上使用@ClassRule批註,這將導致它們在第一個測試之前被激活,然後在最後一個測試之後被清除。

3.3。 JUnit 5環境變量

在JUnit 5測試中使用System Stubs對象之前,必須將擴展添加到我們的測試類中:

@ExtendWith(SystemStubsExtension.class)

 class EnvironmentVariablesJUnit5 {

 // tests

 }

然後,我們可以在測試類中為JUnit 5創建一個字段以供我們管理。 @SystemStub對此進行註釋,以便擴展程序知道如何激活它:

@SystemStub

 private EnvironmentVariables environmentVariables;

該擴展將僅管理標有@SystemStub對象,這允許我們根據需要在測試中手動使用其他System Stubs對象。

在這裡,我們沒有提供存根對象的任何構造。該擴展為我們構造了一個,就像Mockito擴展構造了模擬一樣。

現在,我們可以使用該對象來幫助我們在其中一項測試中設置環境變量:

@Test

 void givenEnvironmentCanBeModified_whenSetEnvironment_thenItIsSet() {

 environmentVariables.set("ENV", "value1");



 assertThat(System.getenv("ENV")).isEqualTo("value1");

 }

如果我們想提供從測試方法外部應用於所有測試的環境變量,則可以在@BeforeEach方法內部進行操作,或者可以使用EnvironmentVariables的構造函數來設置我們的值:

@SystemStub

 private EnvironmentVariables environmentVariables =

 new EnvironmentVariables("ENV", "value1");

EnvironmentVariablesRule ,構造函數有多個重載,這使我們可以通過多種方法來設置所需的變量。如果願意,我們還可以set方法來設置值:

@SystemStub

 private EnvironmentVariables environmentVariables =

 new EnvironmentVariables()

 .set("ENV", "value1")

 .set("ENV2", "value2");

我們還可以將字段static ,以便將它們作為@BeforeAll / @AfterAll生命週期的一部分進行管理。

3.4。 JUnit 5參數注入

當將存根對像用於所有測試時,將其放置在字段中很有用,但我們可能更喜歡僅將它們用於選定的對象。這可以通過JUnit 5參數注入來實現:

@Test

 void givenEnvironmentCanBeModified(EnvironmentVariables environmentVariables) {

 environmentVariables.set("ENV", "value1");



 assertThat(System.getenv("ENV")).isEqualTo("value1");

 }

在這種情況下, EnvironmentVariables對象,從而使我們可以在單個測試中使用它。該對像也已激活,因此可以在運行時環境中運行。測試完成後,它將進行整理。

所有系統存根對像都具有默認構造函數,並且可以在運行時重新配置。我們可以根據需要注入任意數量的測試。

3.5。全面執行環境變量

SystemStubs類獲得用於創建存根的原始System Lambda外觀方法。在內部,它們是通過創建存根對象的實例來實現的。有時,從配方返回的對像是存根對象,用於進一步的配置和使用:

withEnvironmentVariable("ENV3", "val")

 .execute(() -> {

 assertThat(System.getenv("ENV3")).isEqualTo("val");

 });

在後台, withEnvironmentVariable等效於:

return new EnvironmentVariables().set("ENV3", "val");

execute方法是所有SystemStub對象共有的。它設置對象定義的存根,然後執行傳入的lambda。隨後,它整理並把控制權返回給周圍的測試。

如果測試代碼返回一個值,那麼可以通過execute返回該值:

String extracted = new EnvironmentVariables("PROXY", "none")

 .execute(() -> System.getenv("PROXY"));



 assertThat(extracted).isEqualTo("none");

當我們正在測試的代碼需要訪問環境設置來構造某些東西時,這很有用。通常在測試諸如AWS Lambda處理程序之類的東西時使用,通常通過環境變量進行配置。

這種模式偶爾進行測試的優點是,我們必須僅在需要的地方顯式設置存根。因此,它可以更加精確和可見。但是,它不允許我們在測試之間共享設置,並且可能會花費很多時間。

3.6。多個系統存根

我們已經了解了JUnit 4和JUnit 5插件如何為我們構造和激活存根對象。如果有多個存根,則框架代碼將它們適當地設置和拆除。

但是,當我們為執行模式構造存根對象時,我們需要測試代碼才能在其中全部運行。

這可以使用with / execute方法來實現。 execute使用多個存根對象創建一個複合對象而實現的:

with(new EnvironmentVariables("FOO", "bar"), new SystemProperties("prop", "val"))

 .execute(() -> {

 assertThat(System.getenv("FOO")).isEqualTo("bar");

 assertThat(System.getProperty("prop")).isEqualTo("val");

 });

現在,我們已經看到了使用System Stubs對象的一般形式,無論是否支持JUnit框架,讓我們看看該庫的其餘功能。

4.系統屬性

我們可以隨時用Java System.setProperty但是,這冒著將設置從一項測試洩漏到另一項測試的風險。 SystemProperties存根控件的主要目的是在測試完成後將系統屬性還原到其原始設置。但是,對於通用設置代碼來說,定義在測試開始之前應使用哪些系統屬性也很有用。

4.1。 JUnit 4系統屬性

通過將規則添加到JUnit 4測試類,我們可以將每個測試與其他測試方法中進行的System.setProperty我們還可以通過構造函數提供一些前期屬性:

@Rule

 public SystemPropertiesRule systemProperties =

 new SystemPropertiesRule("db.connection", "false");

使用此對象,我們還可以在JUnit @Before方法中設置一些其他屬性:

@Before

 public void before() {

 systemProperties.set("before.prop", "before");

 }

我們還可以在set方法,或者根據需要使用System.setProperty 。我們只能在創建SystemPropertiesRule@Before set ,因為它會將設置存儲在規則中,以備後用。

4.2。 JUnit 5系統屬性

我們有兩個使用SystemProperties對象的主要用例。我們可能希望在每個測試用例之後重置系統屬性,或者我們希望在中央放置一些通用的系統屬性以供每個測試用例使用。

恢復系統屬性需要我們將JUnit 5擴展和SystemProperties字段都添加到我們的測試類中:

@ExtendWith(SystemStubsExtension.class)

 class RestoreSystemProperties {

 @SystemStub

 private SystemProperties systemProperties;



 }

現在,每個測試將具有其以後更改的所有系統屬性。

我們還可以通過參數注入對選定的測試執行此操作:

@Test

 void willRestorePropertiesAfter(SystemProperties systemProperties) {



 }

如果要在測試中設置屬性,則可以在構造SystemProperties對象@BeforeEach方法:

@ExtendWith(SystemStubsExtension.class)

 class SetSomeSystemProperties {

 @SystemStub

 private SystemProperties systemProperties;



 @BeforeEach

 void before() {

 systemProperties.set("beforeProperty", "before");

 }

 }

再次提醒我們,JUnit 5測試需要使用@ExtendWith(SystemStubsExtension.class).如果我們在初始化列表中new語句,則擴展程序將創建System Stubs對象。

4.3。環顧四周的系統屬性

SystemStubs類提供了restoreSystemProperties方法,以允許我們運行具有已還原屬性的測試代碼:

restoreSystemProperties(() -> {

 // test code

 System.setProperty("unrestored", "true");

 });



 assertThat(System.getProperty("unrestored")).isNull();

這需要一個不返回任何值的lambda。如果我們希望使用一個通用的設置函數來創建屬性,從測試方法中獲取返回值,或者with / execute SystemProperties與其他存根相結合,則可以顯式創建該對象:

String result = new SystemProperties()

 .execute(() -> {

 System.setProperty("unrestored", "true");

 return "it works";

 });



 assertThat(result).isEqualTo("it works");

 assertThat(System.getProperty("unrestored")).isNull();

4.4。文件中的屬性

SystemPropertiesEnvironmentVariables對像都可以從Map構造。這允許將Java的Properties對象提供為系統屬性或環境變量的源。

PropertySource類內部有幫助程序方法,用於從文件或資源中加載Java屬性。這些屬性文件是名稱/值對:

name=baeldung

 version=1.0

我們可以使用fromResource函數test.properties

SystemProperties systemProperties =

 new SystemProperties(PropertySource.fromResource("test.properties"));

PropertySource ,其他來源也有類似的便捷方法fromFilefromInputStream

5.系統輸出和系統錯誤

當我們的應用程序寫入System.out,可能很難進行測試。有時可以通過使用接口作為輸出目標並在測試時進行模擬來解決此問題:

interface LogOutput {

 void write(String line);

 }



 class Component {

 private LogOutput log;



 public void method() {

 log.write("Some output");

 }

 }

Mockito這樣的技術可以很好地工作,但是如果我們只能捕獲System.out本身,則沒有必要。

5.1。 JUnit 4 SystemOutRuleSystemErrRule

為了在JUnit 4測試中將輸出捕獲到System.out SystemOutRule

@Rule

 public SystemOutRule systemOutRule = new SystemOutRule();

之後,可以在測試中讀取System.out

System.out.println("line1");

 System.out.println("line2");



 assertThat(systemOutRule.getLines())

 .containsExactly("line1", "line2");

我們可以選擇文本格式。上面的示例使用getLines提供Stream<String> 。我們也可以選擇獲取整個文本塊:

assertThat(systemOutRule.getText())

 .startsWith("line1");

但是,我們應該注意,此文本將包含換行符,這些字符在不同平台之間有所不同。我們可以在每個平台上使用歸一化形式將換行符替換為\n

assertThat(systemOutRule.getLinesNormalized())

 .isEqualTo("line1\nline2\n");

SystemErrRule System.err中的工作方式與在System.out對應方式相同:

@Rule

 public SystemErrRule systemErrRule = new SystemErrRule();



 @Test

 public void whenCodeWritesToSystemErr_itCanBeRead() {

 System.err.println("line1");

 System.err.println("line2");



 assertThat(systemErrRule.getLines())

 .containsExactly("line1", "line2");

 }

還有一個SystemErrAndOutRule類,它將System.outSystem.err同時挖掘到單個緩衝區中。

5.2。 JUnit 5示例

與其他System Stubs對像一樣,我們只需要聲明一個類型為SystemOutSystemErr的字段或參數。這將為我們提供輸出的捕獲:

@SystemStub

 private SystemOut systemOut;



 @SystemStub

 private SystemErr systemErr;



 @Test

 void whenWriteToOutput_thenItCanBeAsserted() {

 System.out.println("to out");

 System.err.println("to err");



 assertThat(systemOut.getLines()).containsExactly("to out");

 assertThat(systemErr.getLines()).containsExactly("to err");

 }

我們還可以使用SystemErrAndOut類將兩組輸出定向到同一緩衝區。

5.3。全面執行示例

SystemStubs外觀提供了一些功能,用於點按輸出並將其作為String返回:

@Test

 void givenTapOutput_thenGetOutput() throws Exception {

 String output = tapSystemOutNormalized(() -> {

 System.out.println("a");

 System.out.println("b");

 });



 assertThat(output).isEqualTo("a\nb\n");

 }

我們應該注意,這些方法沒有提供像原始對象本身一樣豐富的接口。輸出的捕獲不能輕鬆地與其他存根(例如,設置環境變量)組合使用。

但是,可以直接使用SystemOutSystemErr,SystemErrAndOut例如,我們可以將它們與一些SystemProperties結合使用:

SystemOut systemOut = new SystemOut();

 SystemProperties systemProperties = new SystemProperties("a", "!");

 with(systemOut, systemProperties)

 .execute(() -> {

 System.out.println("a: " + System.getProperty("a"));

 });



 assertThat(systemOut.getLines()).containsExactly("a: !");

5.4。靜音

有時,我們的目標不是捕獲輸出,而是防止其混亂我們的測試運行日誌。我們可以使用muteSystemOutmuteSystemErr函數實現此目的:

muteSystemOut(() -> {

 System.out.println("nothing is output");

 });

SystemOutRule在所有測試中實現相同的目的:

@Rule

 public SystemOutRule systemOutRule = new SystemOutRule(new NoopStream());

在JUnit 5中,我們可以使用相同的技術:

@SystemStub

 private SystemOut systemOut = new SystemOut(new NoopStream());

5.5。客制化

如我們所見,攔截輸出有多種變體。它們都在庫中共享一個公共基類。為了方便起見,一些輔助方法和類型(例如SystemErrAndOut,幫助完成常見的事情。但是,該庫本身很容易定制。

我們可以提供自己的目標,以將輸出捕獲為Output的實現。我們已經在第一個示例中看到了使用OutputTapStream NoopStream用於靜音。我們也有DisallowWriteStream ,如果有寫入內容,它將引發錯誤:

// throws an exception:

 new SystemOut(new DisallowWriteStream())

 .execute(() -> System.out.println("boo"));

6.模擬系統

我們可能有一個應用程序讀取stdin上的輸入。測試這一點可能涉及將算法提取到一個可以從任何InputStream讀取的函數中,然後將其提供給預先準備好的輸入流。通常,模塊化代碼會更好,因此這是一個很好的模式。

但是,**如果僅測試核心功能System.in**為源的代碼的測試範圍。

在任何情況下,構造我們自己的流都是不方便的。幸運的是,系統存根提供了所有這些解決方案。

6.1。測試輸入流

系統存根提供了一系列AltInputStream類,作為**InputStream讀取的任何代碼的替代輸入**:

LinesAltStream testInput = new LinesAltStream("line1", "line2");



 Scanner scanner = new Scanner(testInput);

 assertThat(scanner.nextLine()).isEqualTo("line1");

在此示例中,我們使用了一個字符串數組來構造LinesAltStream ,但是我們可以提供Stream<String>的輸入,從而允許將其與任何文本數據源一起使用,而不必將其全部一次加載到內存中。 。

6.2。 JUnit 4示例

SystemInRule在JUnit 4測試中提供用於輸入的行:

@Rule

 public SystemInRule systemInRule =

 new SystemInRule("line1", "line2", "line3");

然後,測試代碼可以從System.in讀取以下輸入:

@Test

 public void givenInput_canReadFirstLine() {

 assertThat(new Scanner(System.in).nextLine())

 .isEqualTo("line1");

 }

6.3。 JUnit 5示例

對於JUnit 5測試,我們創建一個SystemIn字段:

@SystemStub

 private SystemIn systemIn = new SystemIn("line1", "line2", "line3");

然後我們的測試將在System.in運行,並提供這些行作為輸入。

6.4。全面執行示例

SystemStubs外觀提供withTextFromSystemIn作為工廠方法,該方法創建一個SystemIn對象供其execute方法使用:

withTextFromSystemIn("line1", "line2", "line3")

 .execute(() -> {

 assertThat(new Scanner(System.in).nextLine())

 .isEqualTo("line1");

 });

6.5。客制化

可以在構造時或在測試中運行時將更多功能添加到SystemIn

我們可以調用andExceptionThrownOnInputEnd ,當System.in的文本用完時,這將導致從System.in這可以模擬從文件讀取的中斷。

我們還可以使用setInputStream將輸入流設置為來自任何InputStream ,例如FileInputStream 。我們還有LinesAltStreamTextAltStream ,它們對輸入文本進行操作。

7.模擬系統退出

如前所述,如果我們的代碼可以調用System.exit ,則可能導致危險且難以調試測試錯誤。 System.exit的目的之一是使意外調用成為可跟踪的錯誤。另一個動機是測試軟件的故意退出。

7.1。 JUnit 4示例

讓我們將SystemExitRule添加到測試類中作為一種安全措施,以防止任何System.exit停止JVM:

@Rule

 public SystemExitRule systemExitRule = new SystemExitRule();

但是,我們也可能希望查看是否使用了正確的退出代碼。為此,我們需要斷言代碼拋出AbortExecutionException ,這是調用System.exit的系統存根信號。

@Test

 public void whenExit_thenExitCodeIsAvailable() {

 assertThatThrownBy(() -> {

 System.exit(123);

 }).isInstanceOf(AbortExecutionException.class);



 assertThat(systemExitRule.getExitCode()).isEqualTo(123);

 }

在此示例中,我們使用了來自AssertJ assertThatThrownBy來捕獲並檢查發生異常信號退出的情況。然後,我們查看了SystemExitRule getExitCode以聲明退出代碼。

7.2。 JUnit 5示例

對於JUnit 5測試,我們聲明@SystemStub字段:

@SystemStub

 private SystemExit systemExit;

然後我們用SystemExit類以同樣的方式作為SystemExitRule JUnit 4中考慮到SystemExitRule類的子類SystemExit ,它們具有相同的接口。

7.3。全面執行示例

SystemStubs類提供catchSystemExit,它在內部使用SystemExitexecute函數:

int exitCode = catchSystemExit(() -> {

 System.exit(123);

 });

 assertThat(exitCode).isEqualTo(123);

與JUnit插件示例相比,此代碼不會引發異常以指示系統退出。而是,它捕獲錯誤並記錄退出代碼。使用Facade方法,它返回退出代碼。

當我們execute方法時,將捕獲出口,並在SystemExit像中設置出口代碼。然後,我們可以調用getExitCode獲取退出代碼,如果沒有退出代碼,則返回null

8. JUnit 5中的自定義測試資源

JUnit 4已經提供了一種簡單的結構來創建測試規則,例如係統存根中使用的那些規則。如果要通過設置和拆卸來為某些資源創建新的測試規則,則可以將ExternalResource子類化,並提供beforeafter方法的替代。

JUnit 5具有更複雜的資源管理模式。對於簡單的用例,可以將System Stubs庫用作起點。 SystemStubsExtension在滿足TestResource接口的TestResource

8.1。創建一個TestResource

TestResource的子類,然後以與使用系統存根相同的方式使用自定義對象。我們應該注意,如果要使用字段和參數的自動創建,我們需要提供一個默認的構造函數。

假設我們想打開到數據庫的連接以進行一些測試,然後再將其關閉:

public class FakeDatabaseTestResource implements TestResource {

 // let's pretend this is a database connection

 private String databaseConnection = "closed";



 @Override

 public void setup() throws Exception {

 databaseConnection = "open";

 }



 @Override

 public void teardown() throws Exception {

 databaseConnection = "closed";

 }



 public String getDatabaseConnection() {

 return databaseConnection;

 }

 }

我們使用databaseConnection字符串作為諸如數據庫連接之類的資源的說明。 setupteardown方法中修改資源的狀態。

8.2。全面執行是內置的

現在,讓我們嘗試將它與執行模式一起使用:

FakeDatabaseTestResource fake = new FakeDatabaseTestResource();

 assertThat(fake.getDatabaseConnection()).isEqualTo("closed");



 fake.execute(() -> {

 assertThat(fake.getDatabaseConnection()).isEqualTo("open");

 });

如我們所見, TestResource接口為它提供了其他對象的執行能力。

8.3。 JUnit 5測試中的自定義TestResource

我們也可以在JUnit 5測試中使用它:

@ExtendWith(SystemStubsExtension.class)

 class FakeDatabaseJUnit5UnitTest {



 @Test

 void useFakeDatabase(FakeDatabaseTestResource fakeDatabase) {

 assertThat(fakeDatabase.getDatabaseConnection()).isEqualTo("open");

 }

 }

因此,很容易創建遵循系統存根設計的其他測試對象。

9. JUnit 5春季測試的環境和屬性替代

為Spring測試設置環境變量可能很困難。我們可能會為集成測試編寫一個自定義規則,以設置一些系統屬性以供Spring使用。

我們還可以使用ApplicationContextInitializer類來插入我們的Spring Context,從而為測試提供額外的屬性。

由於許多Spring應用程序是由系統屬性或環境變量替代控制的,因此使用Spring Stub作為內部類運行時,使用System Stub在外部測試中進行設置可能會更容易。

系統存根文檔中提供了完整的示例。我們首先創建一個外部類:

@ExtendWith(SystemStubsExtension.class)

 public class SpringAppWithDynamicPropertiesTest {



 // sets the environment before Spring even starts

 @SystemStub

 private static EnvironmentVariables environmentVariables;

 }

在這種情況下,@ SystemStub字段是static @BeforeAll方法中進行了初始化:

@BeforeAll

 static void beforeAll() {

 String baseUrl = ...;



 environmentVariables.set("SERVER_URL", baseUrl);

 }

測試生命週期中的這一點允許在Spring測試運行之前創建一些全局資源並將其應用於運行環境。

然後,我們可以將Spring測試放入@Nested類。這導致僅在設置父類時才運行它:

@Nested

 @SpringBootTest(classes = {RestApi.class, App.class},

 webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

 class InnerSpringTest {

 @LocalServerPort

 private int serverPort;



 // Test methods

 }

將根據外部類中SystemStub對象設置的環境狀態創建Spring上下文。

這項技術還使我們能夠控制其他任何庫的配置,這些庫依賴於可能在Spring Beans之後運行的系統屬性或環境變量的狀態。

這可以讓我們進入測試生命週期,以在運行Spring測試之前修改諸如代理設置或HTTP連接池參數之類的內容。

10.結論

在本文中,我們研究了能夠模擬系統資源的重要性,以及系統存根如何通過其JUnit 4和JUnit 5插件以最少的代碼重複來實現存根的複雜配置。

我們在測試中看到瞭如何提供和隔離環境變量和系統屬性。然後,我們著眼於捕獲輸出並控制標準流上的輸入。我們還研究了捕獲和斷言對System.exit調用。

最後,我們研究瞭如何創建自定義測試資源以及如何在Spring中使用System Stub。