LoginSignup
0
0

More than 1 year has passed since last update.

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

Posted at

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に入る前に例外が発生したパターン

サンプルコード


public class TryWithResources {

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

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

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

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

			printLine("ビジネスロジック:終了");
		} catch (Throwable t) {
			// 例外発生
			printLine("(catch)" + t.getMessage());
		}
		// コンパイル後展開されるfinally節では、以下の分岐が発生
		// 1.リソースが非nullで、リソース破棄が正常終了した
		// 2.リソースが非nullで、リソース破棄で異常が発生した
		// 3.リソースが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("テスト1:全て正常");
		try {
			perform(LogicCases.NORMAL, ResourceCases.NORMAL);
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

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

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

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

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

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

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

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

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

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

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

//    << コンソールへの出力内容 >>
//
//    テストケースを開始します。
//    テスト6:ビジネスロジック=正常/リソース=異常発生
//    [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);
	}
	// ~~ここまでログ出力関連の処理~~
}


以上です。

0
0
0

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
  3. You can use dark theme
What you can do with signing up
0
0