1. 概要

単体テストを作成するときに、Systemクラスと直接対話するコードをテストする必要がある場合があります。 通常、 System.exit を直接呼び出すか、System.inを使用して引数を読み取るコマンドラインツールなどのアプリケーション。

このチュートリアルでは、システムクラスを使用するコードをテストするための一連のJUnitルールを提供するシステムルールと呼ばれるきちんとした外部ライブラリの最も一般的な機能を見ていきます。

2. Mavenの依存関係

まず、システムルールの依存関係pom.xmlに追加しましょう。

<dependency>
    <groupId>com.github.stefanbirkner</groupId>
    <artifactId>system-rules</artifactId>
    <version>1.19.0</version>
</dependency>

また、 MavenCentralからも利用できるSystemLambda依存関係を追加します。

<dependency>
    <groupId>com.github.stefanbirkner</groupId>
    <artifactId>system-lambda</artifactId>
    <version>1.1.0</version>
</dependency>

システムルールはJUnit5を直接サポートしていないため、最後の依存関係を追加しました。これにより、テストで使用するSystemLambdaラッパーメソッドが提供されます。 これに代わる拡張機能ベースのシステムスタブがあります。

3. システムプロパティの操作

簡単に要約すると、Javaプラットフォームはプロパティオブジェクトを使用して、ローカルシステムと構成に関する情報を提供します。 プロパティを簡単に印刷できます。

System.getProperties()
  .forEach((key, value) -> System.out.println(key + ": " + value));

ご覧のとおり、プロパティには、現在のユーザー、Javaランタイムの現在のバージョン、ファイルパス名の区切り文字などの情報が含まれています。

java.version: 1.8.0_221
file.separator: /
user.home: /Users/baeldung
os.name: Mac OS X
...

System.setProperty メソッドを使用して、独自のシステムプロパティを設定することもできます。 これらのプロパティはJVMグローバルであるため、テストのシステムプロパティを操作する場合は注意が必要です。

たとえば、システムプロパティを設定する場合、テストの終了時または失敗が発生したときに、プロパティを元の値に復元する必要があります。 これにより、面倒なセットアップやコードの破棄につながる場合があります。 ただし、これを怠ると、テストで予期しない副作用が発生する可能性があります。

次のセクションでは、テストが完了した後、簡潔かつ簡単な方法でシステムプロパティ値を提供、クリーンアップ、および復元する方法を説明します。

4. システムプロパティの提供

ログを書き込む場所を含むシステムプロパティlog_dirがあり、アプリケーションが起動時にこの場所を設定するとします。

System.setProperty("log_dir", "/tmp/baeldung/logs");

4.1. 単一のプロパティを提供する

ここで、単体テストから、別の値を提供したいと考えてみましょう。 これは、ProvideSystemPropertyルールを使用して実行できます。

public class ProvidesSystemPropertyWithRuleUnitTest {

    @Rule
    public final ProvideSystemProperty providesSystemPropertyRule = new ProvideSystemProperty("log_dir", "test/resources");

    @Test
    public void givenProvideSystemProperty_whenGetLogDir_thenLogDirIsProvidedSuccessfully() {
        assertEquals("log_dir should be provided", "test/resources", System.getProperty("log_dir"));
    }
    // unit test definition continues
}

ProvideSystemPropertyルールを使用して、テストで使用する特定のシステムプロパティに任意の値を設定できます。この例では、log_dirプロパティをtest/に設定します。 resources ディレクトリ、および単体テストから、テストプロパティ値が正常に提供されたことを表明するだけです。

次に、テストクラスが完了したときに、log_dirプロパティの値を出力するとします。

@AfterClass
public static void tearDownAfterClass() throws Exception {
    System.out.println(System.getProperty("log_dir"));
}

プロパティの値が元の値に復元されていることがわかります。

/tmp/baeldung/logs

4.2. 複数のプロパティを提供する

複数のプロパティを提供する必要がある場合は、およびメソッドを使用して、テストに必要な数のプロパティ値をチェーンできます。

@Rule
public final ProvideSystemProperty providesSystemPropertyRule = 
    new ProvideSystemProperty("log_dir", "test/resources").and("another_property", "another_value")

4.3. ファイルからのプロパティの提供

同様に、 ProvideSystemProperty ルールを使用して、ファイルまたはクラスパスリソースからプロパティを提供することもできます。

@Rule
public final ProvideSystemProperty providesSystemPropertyFromFileRule = 
  ProvideSystemProperty.fromResource("/test.properties");

@Test
public void givenProvideSystemPropertyFromFile_whenGetName_thenNameIsProvidedSuccessfully() {
    assertEquals("name should be provided", "baeldung", System.getProperty("name"));
    assertEquals("version should be provided", "1.0", System.getProperty("version"));
}

上記の例では、クラスパスにtest.propertiesファイルがあると想定しています。

name=baeldung
version=1.0

4.4. JUnit5とLambdasによるプロパティの提供

前述したように、ライブラリのSystem Lambdaバージョンを使用して、JUnit5と互換性のあるテストを実装することもできます。

このバージョンのライブラリを使用してテストを実装する方法を見てみましょう。

@BeforeAll
static void setUpBeforeClass() throws Exception {
    System.setProperty("log_dir", "/tmp/baeldung/logs");
}

@Test
void givenSetSystemProperty_whenGetLogDir_thenLogDirIsProvidedSuccessfully() throws Exception {
    restoreSystemProperties(() -> {
        System.setProperty("log_dir", "test/resources");
        assertEquals("log_dir should be provided", "test/resources", System.getProperty("log_dir"));
    });

    assertEquals("log_dir should be provided", "/tmp/baeldung/logs", System.getProperty("log_dir"));
}

このバージョンでは、 restoreSystemProperties メソッドを使用して、特定のステートメントを実行できます。 このステートメント内で、システムプロパティに必要な値を設定して提供できます。 このメソッドの実行が終了するとわかるように、log_dirの値は/tmp / baeldung /logsの前と同じです。

残念ながら、restoreSystemPropertiesメソッドを使用してファイルからプロパティを提供するための組み込みのサポートはありません。

5. システムプロパティのクリア

テストの開始時に一連のシステムプロパティをクリアし、テストが成功したか失敗したかに関係なく、テストが終了したときに元の値を復元したい場合があります。

この目的のためにClearSystemPropertiesルールを使用できます。

@Rule
public final ClearSystemProperties userNameIsClearedRule = new ClearSystemProperties("user.name");

@Test
public void givenClearUsernameProperty_whenGetUserName_thenNull() {
    assertNull(System.getProperty("user.name"));
}

システムプロパティuser.nameは、事前定義されたシステムプロパティの1つであり、ユーザーアカウント名が含まれています。 上記の単体テストで予想されたように、このプロパティをクリアし、テストから空であることを確認します。

便利なことに、ClearSystemPropertiesコンストラクターに複数のプロパティ名を渡すこともできます。

6. System.inをあざける

時々、System.inから読み取るインタラクティブなコマンドラインアプリケーションを作成することがあります。

このセクションでは、標準の入力から名と名前を読み取り、それらを連結する非常に単純な例を使用します。

private String getFullname() {
    try (Scanner scanner = new Scanner(System.in)) {
        String firstName = scanner.next();
        String surname = scanner.next();
        return String.join(" ", firstName, surname);
    }
}

システムルールには、 TextFromStandardInputStream ルールが含まれています。これを使用して、System.inを呼び出すときに提供する必要のある行を指定できます。

@Rule
public final TextFromStandardInputStream systemInMock = emptyStandardInputStream();

@Test
public void givenTwoNames_whenSystemInMock_thenNamesJoinedTogether() {
    systemInMock.provideLines("Jonathan", "Cook");
    assertEquals("Names should be concatenated", "Jonathan Cook", getFullname());
}

これを実現するには、providesLines メソッドを使用します。このメソッドは、 varargs パラメーターを使用して、複数の値を指定できるようにします。

この例では、 getFullname メソッドを呼び出す前に、2つの値を指定します。ここで、System.inが参照されます。 scanner.next()を呼び出すたびに、提供された2つの行の値が返されます。

SystemLambdaを使用したJUnit5バージョンのテストで同じことを実現する方法を見てみましょう。

@Test
void givenTwoNames_whenSystemInMock_thenNamesJoinedTogether() throws Exception {
    withTextFromSystemIn("Jonathan", "Cook").execute(() -> {
        assertEquals("Names should be concatenated", "Jonathan Cook", getFullname());
    });
}

このバリエーションでは、同様の名前の withTextFromSystemIn メソッドを使用します。これにより、提供されたSystem.in値を指定できます。

どちらの場合も、テストが終了すると、System.inの元の値が復元されることに注意してください。

7. System.outおよびSystem.errのテスト

前のチュートリアルでは、システムルールを使用して System.out.println()。を単体テストする方法を説明しました。

便利なことに、標準エラーストリームと相互作用するコードをテストするためにほぼ同じアプローチを適用できます。 今回はSystemErrRuleを使用します。

@Rule
public final SystemErrRule systemErrRule = new SystemErrRule().enableLog();

@Test
public void givenSystemErrRule_whenInvokePrintln_thenLogSuccess() {
    printError("An Error occurred Baeldung Readers!!");

    Assert.assertEquals("An Error occurred Baeldung Readers!!", 
      systemErrRule.getLog().trim());
}

private void printError(String output) {
    System.err.println(output);
}

良い! SystemErrRule を使用して、System.errへの書き込みをインターセプトできます。 まず、ルールで enableLog メソッドを呼び出して、System.errに書き込まれたすべてのログを記録し始めます。 次に、 enableLog を呼び出したので、 getLog を呼び出して、System.errに書き込まれたテキストを取得します。

それでは、テストのJUnit5バージョンを実装しましょう。

@Test
void givenTapSystemErr_whenInvokePrintln_thenOutputIsReturnedSuccessfully() throws Exception {

    String text = tapSystemErr(() -> {
        printError("An error occurred Baeldung Readers!!");
    });

    Assert.assertEquals("An error occurred Baeldung Readers!!", text.trim());
}

このバージョンでは、ステートメントを実行してSystem.errに渡されたコンテンツをキャプチャできるtapSystemErrメソッドを使用します。

8. System.exitの処理

コマンドラインアプリケーションは通常、System.exitを呼び出すことで終了します。 このようなアプリケーションをテストする場合、System.exitを呼び出すコードが検出されると、テストが終了する前に異常終了する可能性があります。

ありがたいことに、システムルールは、ExpectedSystemExitルールを使用してこれを処理するための優れたソリューションを提供します。

@Rule
public final ExpectedSystemExit exitRule = ExpectedSystemExit.none();

@Test
public void givenSystemExitRule_whenAppCallsSystemExit_thenExitRuleWorkssAsExpected() {
    exitRule.expectSystemExitWithStatus(1);
    exit();
}

private void exit() {
    System.exit(1);
}

ExpectedSystemExit ルールを使用すると、テストから予想される System.exit()呼び出しを指定できます。 この簡単な例では、expectedSystemExitWithStatusメソッドを使用して予想されるステータスコードも確認します。

catchSystemExitメソッドを使用して、JUnit5バージョンで同様のことを実現できます。

@Test
void givenCatchSystemExit_whenAppCallsSystemExit_thenStatusIsReturnedSuccessfully() throws Exception {
    int statusCode = catchSystemExit(() -> {
        exit();
    });
    assertEquals("status code should be 1:", 1, statusCode);
}

9. 結論

要約すると、このチュートリアルでは、システムルールライブラリについて詳しく説明しました。

まず、システムプロパティを使用するコードをテストする方法を説明することから始めました。 次に、標準出力と標準入力をテストする方法を確認しました。 最後に、テストからSystem.exitを呼び出すコードを処理する方法を確認しました。

システムルールライブラリは、テストから環境変数と特別なセキュリティマネージャーを提供するためのサポートも提供します。 詳細については、ドキュメント全体を確認してください。

いつものように、記事の完全なソースコードは、GitHubから入手できます。