search
LoginSignup
19
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

foobar Advent Calendar 2015 Day 5

posted at

updated at

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

はじめに

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 は、読んでおいて損はないと思います!!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
19
Help us understand the problem. What are the problem?