JUnit System.exit するメソッドをテストする

  • 7
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

Javaのバッチ処理では、
メソッドの最後に System#exit(int) を呼び出すことがあります。
JVMを終了して、Exitコードを返すのですが。

問題は、
そのメソッドをテストしようとした時にJUnitのランナーも停止するので、
assert句まで到達せずにテストが終了してしまうことです。

@Test
public void testExitMethod() {

    // System.exit(int); するメソッドを呼び出す
    TestMain.main(); // ここで終了する

    assertThat(hoge ,is(hoge)); // ここまで到達しない  
}

ちなみに、実行するとJUnitの結果が、グリーンでもレッドでもなく灰色になります。

方法

JUnitでSystem.exit(int);するメソッドをテストする方法を2通り紹介します。

1. exitコードをテストする。

2. メソッドの処理をテストする。(exitをさせなくする)

1. exitコードをassertする。

  • このクラスをテストします。
ExitSample1.java
public class ExitSample1 {

    public static void main(String[] args) {

        if (args.length == 0) {
            System.exit(1); // JVMが終了します        
        }

        System.exit(0); // JVMが終了します
    }
}
  • テストコード

System Rule をライブラリに追加。

ExitSample1Test.java
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ExpectedSystemExit;

public class ExitSimple1Test {

    // @Ruleアノテーションと共に、ExpectedSystemExitクラスのインスタンス作成
    @Rule
    public final ExpectedSystemExit exit = ExpectedSystemExit.none();

    @Test
    public void fail() {
        // これだと途中で止まる
        ExitSample1.main(new String[] {});
    }

    @Test
    public void success() {
        // 終了コードの期待値をExitするメソッドを、呼ぶ前に宣言
        exit.expectSystemExitWithStatus(1);

        ExitSample1.main(new String[] {});
    }
}

これでSystem#exit(int); のExitコードをテストできます。

2. exitをさせなくする。

実際の業務では、Exitコードだけでなく。
するメソッドの処理結果をassertしたいときの方が多いと思います。

そんな時は、SecurityManagerクラス を使って呼び出し元に、
JVMからExitする権限を上書きする必要があります。

つまり、 System.exit(int); を呼んでも、JVMを終了できなくしてしまえばいいのです。


  • このメソッドをテストする
ExitSsmple2.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.junit.Test;

public class ExitSsmple2 {

    public static void main(String[] args) {
        String filePath = "C:\\ExitTest";
        String fileName = args[0];  
        Path fullPath = Paths.get(filePath, fileName);

        try {
            // 適当なディレクトリにファイル作成
            Files.createFile(fullPath);
        } catch (IOException e) {
            // エラーならEixt
            System.exit(1);
        }

        // 最後にExit
        System.exit(0);
    }
}

適当なディレクトリに、ファイルを作成するクラスです。
exitコードのテストもしたいですが、ファイルが作成されていることもテストしたいです。

失敗例


  • 普通にやろうとするとこうなります。
ExitSsmple2Test.java

import static org.junit.Assert.*;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.junit.Test;

public class ExitSample2Test {

    @Test
    public void fail() {
        String filePath = "C:\\ExitTest";
        String fileName = "sample.txt"; 
        Path fullPath = Paths.get(filePath, fileName);

        ExitSsmple2.main(new String[]{fileName}); // ここで終了してしまう

        // ファイルが作成されたかテストしたいが、ここで到達しない...
        boolean result = Files.exists(fullPath);

        assertTrue(result);
    }
}

手順


  1. 独自Exceptinを作成(SecurityExceptionを継承してれば名前は何でもいいです)
  2. exitなどの権限を扱うcheckPermission(Permission)を上書き(Override)
  3. exitメソッドが呼ばれたときに、Exceptionを投げるように上書き(Override)
  4. 設定した情報をsetする
  5. exitの代わりに、(手順1で)作成したExitExceptionをcatch

やってみます。

成功例


  • 成功サンプル
ExitSsmple2Test.java
import static org.junit.Assert.*;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Permission;

import org.junit.Before;
import org.junit.Test;

public class ExitSsmple2Test {

    // 手順1 独自Exceptinを作成(SecurityExceptionを継承してれば名前は何でもいいです)
    class ExitException extends SecurityException {
        private static final long serialVersionUID = 1L;
        public int state = 1;

        public ExitException(int state) {
            this.state = state;
        }
    }

    @Before
    public void setUp() {
        // SecurityManagerのインスタンス作成
        SecurityManager securityManager = new SecurityManager() {

            // 手順2 exitなどの権限を扱うcheckPermission(Permission)を上書き(Override)
            @Override
            public void checkPermission(Permission permission) {
                // 何書いてもいい
            }

            // 手順3 exitメソッドが呼ばれたときに、Exceptionを投げるように上書き(Override)
            @Override
            public void checkExit(int status) {
                throw new ExitException(status);
            }
        };
        // 手順4 設定した情報をsetする
        System.setSecurityManager(securityManager);
    }

    @Test
    public void success() {
        String filePath = "C:\\ExitTest";
        String fileName = "sample.txt"; 
        Path fullPath = Paths.get(filePath, fileName);

        try {
            ExitSsmple2.main(new String[]{fileName}); 

            // 手順5 exitの代わりに、(手順1で)作成したExitExceptionをcatch
        } catch (ExitException e) {
            // 手順6 catch説の中にassert句を記述する
            boolean result = Files.exists(fullPath);

            assertTrue(result);
        }
    }
}

実行するとグリーンになります。
無事、System#exit(int) のテストができました!!

まとめ

バッチ処理でExitすることは最近はない?らしいですが。
個人的にはまったのと、Java言語側からJVMの権限を操作するのが楽しかったのでメモ。

いけてるWebエンジニアの方々にはあまり興味のない内容かもしれませんが、
Javaエンジニアであれば SecurityManagerのJavaDoc は、読んでおいて損はないと思います!!