はじめに
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する。
- このクラスをテストします。
public class ExitSample1 {
public static void main(String[] args) {
if (args.length == 0) {
System.exit(1); // JVMが終了します
}
System.exit(0); // JVMが終了します
}
}
- テストコード
System Rule をライブラリに追加。
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を終了できなくしてしまえばいいのです。
- このメソッドをテストする
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コードのテストもしたいですが、ファイルが作成されていることもテストしたいです。
失敗例
- 普通にやろうとするとこうなります。
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);
}
}
手順
- 独自Exceptinを作成(SecurityExceptionを継承してれば名前は何でもいいです)
- exitなどの権限を扱うcheckPermission(Permission)を上書き(Override)
- exitメソッドが呼ばれたときに、Exceptionを投げるように上書き(Override)
- 設定した情報をsetする
- exitの代わりに、(手順1で)作成したExitExceptionをcatch
やってみます。
成功例
- 成功サンプル
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 は、読んでおいて損はないと思います!!