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

# はじめに

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

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

# 例

```java
@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する。

* このクラスをテストします。 

```java: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]( http://stefanbirkner.github.io/system-rules/download.html) をライブラリに追加。

```java: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する権限を上書きする必要があります。

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

***

* このメソッドをテストする

```java: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コードのテストもしたいですが、**ファイルが作成されていることもテスト**したいです。

### 失敗例
***

* 普通にやろうとするとこうなります。

```java: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

やってみます。

### 成功例
***



* 成功サンプル

```java: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](http://docs.oracle.com/javase/jp/8/api/java/lang/SecurityManager.html) は、読んでおいて損はないと思います！！