レガシー環境でtry-with-resourcesのC1カバレッジを網羅できない場合の対処法


Java 1.8／JUnit 4等のレガシーな開発環境において、単体テストでC1網羅を求められるケースがありました。
関連するライブラリの過去バージョンでは、カバレッジ検出時にコンパイラがコンパイル後に出力した分岐が未到達とされ、一見しただけでは対処しにくい状況になります。
以下に対処方法をまとめたため、同事例に遭遇した方の一助となれば幸いです。

## 問題
- catchの行で分岐が8あると表示される
※tryブロック内の書きようによっては8以上になる
- なぜか1パターンだけどうしても通らない

## 結論
以下を網羅する。

 1. try→finally（tryが正常終了）
    1. 取得したリソースが非nullで、リソース破棄が正常終了
    2. 取得したリソースがnull
 2. try→finally→catch（tryで異常発生）
    1. 取得したリソースがnull
    2. 取得したリソースが非nullで、リソース破棄が正常終了
    3. 取得したリソースが非nullで、リソース破棄が異常終了
 3. resources→finally→catch
 ※tryに入る前に例外が発生したパターン

## サンプルコード

```Java

public class TryWithResources {

	/**
	 * テスト対象の処理
	 */
	public void perform(Logic logic, CloseableFactory resourceFactory) throws IOException {

        // ※ログ用メソッドの詳細は最下部参照
		printLine("try-with-resources：開始");

		// try-with-resources句
		try (
				// パターンは三通り。
				// １．正常（例外なしでリソースを取得）
				// ２．nullを取得（nullを取得しても例外にはならない）
				// ３．例外が発生
				Closeable resource = resourceFactory.generate()) {
			printLine("ビジネスロジック：開始");

			// 処理の本体
			logic.doLogic();

			printLine("ビジネスロジック：終了");
		} catch (Throwable t) {
			// 例外発生
			printLine("(catch)" + t.getMessage());
		}
		// コンパイル後展開されるfinally節では、以下の分岐が発生
		// １．リソースが非nullで、リソース破棄が正常終了した
		// ２．リソースが非nullで、リソース破棄で異常が発生した
		// ３．リソースがnullだった
		printLine("try-with-resources：終了");
	}

	/**
	 * ビジネスロジックの実行インタフェース
	 */
	interface Logic {
		public void doLogic() throws Throwable;
	}

	/**
	 * リソースのファクトリ・インタフェース
	 */
	interface CloseableFactory {
		public Closeable generate() throws Throwable;
	}

	/**
	 * ビジネスロジックの処理パターン
	 */
	static enum LogicCases implements Logic {

		/** 正常 */
		NORMAL {
			@Override
			public void doLogic() throws Throwable {
				printLine("ビジネスロジック：正常");
			}
		},

		/** 異常 */
		ERROR {
			@Override
			public void doLogic() throws Throwable {
				printLine("ビジネスロジック：異常");
				throw new Throwable("異常：ビジネスロジック");
			}
		};

        @Override
		public abstract void doLogic() throws Throwable;
	}

	/**
	 * リソースの処理パターン
	 */
	static enum ResourceCases implements CloseableFactory {

		/** リソースがnullのケース */
		NULL_RESOURCE {
			@Override
			public Closeable generate() {
				printLine("リソース取得：null");
				return null;
			}
		},

		/** リソース取得時に例外が発生したケース */
		ERROR_IN_RESOURCE {
			@Override
			public Closeable generate() throws Throwable {
				printLine("リソース取得：例外発生");
				throw new Throwable("異常：リソース取得");
			}
		},

		/** 正常ケース */
		NORMAL {
			@Override
			public Closeable generate() {
				printLine("リソース取得：正常");
				return new Closeable() {

					@Override
					public void close() throws IOException {
						printLine("リソース破棄：正常");
					}

				};
			}
		},

		/** リソースのclose時に例外が発生したケース */
		ERROR_IN_CLOSE {
			@Override
			public Closeable generate() {
				return new Closeable() {

					@Override
					public void close() throws IOException {
						printLine("リソース破棄：異常");
						throw new IOException("異常：リソース破棄");
					}

				};
			}
		};

        @Override
		public abstract Closeable generate() throws Throwable;
	}

// ～～以下、テストケース～～

	/**
	 * ビジネスロジック／リソース取得／リソース破棄が全て正常終了した
	 */
	@Test
	public void test1() {
		System.out.println("テスト１：全て正常");
		try {
			perform(LogicCases.NORMAL, ResourceCases.NORMAL);
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

//    << コンソールへの出力内容 >>
//
//    テストケースを開始します。
//    テスト１：全て正常
//    [1] try-with-resources：開始
//    [2] リソース取得：正常
//    [3] ビジネスロジック：開始
//    [4] ビジネスロジック：正常
//    [5] ビジネスロジック：終了
//    [6] リソース破棄：正常
//    [7] try-with-resources：終了
//    テストケースを終了します。

	/**
	 * ビジネスロジック＝正常／リソース＝null
	 */
	@Test
	public void test2() {
		System.out.println("テスト２：ビジネスロジック＝正常／リソース＝null");
		try {
			perform(LogicCases.NORMAL, ResourceCases.NULL_RESOURCE);
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

//    << コンソールへの出力内容 >>
//
//    テストケースを開始します。
//    テスト２：ビジネスロジック＝正常／リソース＝null
//    [1] try-with-resources：開始
//    [2] リソース取得：null
//    [3] ビジネスロジック：開始
//    [4] ビジネスロジック：正常
//    [5] ビジネスロジック：終了
//    [6] try-with-resources：終了
//    テストケースを終了します。

	/**
	 * ビジネスロジック＝異常／リソース＝null
	 */
	@Test
	public void test3() {
		System.out.println("テスト３：ビジネスロジック＝異常／リソース＝null");
		try {
			perform(LogicCases.ERROR, ResourceCases.NULL_RESOURCE);
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

//    << コンソールへの出力内容 >>
//
//    テストケースを開始します。
//    テスト３：ビジネスロジック＝異常／リソース＝null
//    [1] try-with-resources：開始
//    [2] リソース取得：null
//    [3] ビジネスロジック：開始
//    [4] ビジネスロジック：異常
//    [5] (catch)異常：ビジネスロジック
//    [6] try-with-resources：終了
//    テストケースを終了します。

	/**
	 * ビジネスロジック＝異常／リソース取得＆破棄＝正常
	 */
	@Test
	public void test4() {
		System.out.println("テスト４：ビジネスロジック＝異常／リソース取得＆破棄＝正常");
		try {
			perform(LogicCases.ERROR, ResourceCases.NORMAL);
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

//    << コンソールへの出力内容 >>
//
//    テストケースを開始します。
//    テスト４：ビジネスロジック＝異常／リソース取得＆破棄＝正常
//    [1] try-with-resources：開始
//    [2] リソース取得：正常
//    [3] ビジネスロジック：開始
//    [4] ビジネスロジック：異常
//    [5] リソース破棄：正常
//    [6] (catch)異常：ビジネスロジック
//    [7] try-with-resources：終了
//    テストケースを終了します。

	/**
	 * ビジネスロジック＝異常／リソース取得＝正常／リソース破棄＝異常
	 */
	@Test
	public void test5() {
		System.out.println("テスト５：ビジネスロジック＝異常／リソース取得＝正常／リソース破棄＝異常");
		try {
			perform(LogicCases.ERROR, ResourceCases.ERROR_IN_CLOSE);
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

//    << コンソールへの出力内容 >>
//
//    テストケースを開始します。
//    テスト５：ビジネスロジック＝異常／リソース取得＝正常／リソース破棄＝異常
//    [1] try-with-resources：開始
//    [2] ビジネスロジック：開始
//    [3] ビジネスロジック：異常
//    [4] リソース破棄：異常
//    [5] (catch)異常：ビジネスロジック
//    [6] try-with-resources：終了
//    テストケースを終了します。

	/**
	 * ビジネスロジック＝正常／リソース＝異常発生
	 */
	@Test
	public void test6() {
		System.out.println("テスト６：ビジネスロジック＝正常／リソース＝異常発生");
		try {
			perform(LogicCases.NORMAL, ResourceCases.ERROR_IN_RESOURCE);
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

//    << コンソールへの出力内容 >>
//
//    テストケースを開始します。
//    テスト６：ビジネスロジック＝正常／リソース＝異常発生
//    [1] try-with-resources：開始
//    [2] リソース取得：例外発生
//    [3] (catch)異常：リソース取得
//    [4] try-with-resources：終了
//    テストケースを終了します。

	// ～～ここからログ出力関連の処理～～
	@Before
	public void setup() {
		_LINE_NUM = 1;
		System.out.println("テストケースを開始します。");
	}

	@After
	public void tearDown() {
		System.out.println("テストケースを終了します。");
		System.out.println();
	}

	private static int _LINE_NUM = 1;

	public static void printLine(String message) {
		System.out.println("[" + _LINE_NUM++ + "] " + message);
	}
	// ～～ここまでログ出力関連の処理～～
}


```

以上です。