かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot 2.0.x の Web アプリを 2.1.x へバージョンアップする ( その5 )( テストが大量に失敗する原因を解消する2 )

概要

記事一覧はこちらです。

Spring Boot 2.0.x の Web アプリを 2.1.x へバージョンアップする ( その4 )( テストが大量に失敗する原因を解消する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • ksbysample.webapp.lending.SampleHelperTest$異常処理のテスト.SampleHelper_encryptを呼ぶとRuntimeExceptionをthrowする テストメソッドだけ gradle の build タスクから実行された時には成功するのに IntelliJ IDEA の「Run ‘All Tests’」から実行された時には失敗するので、その原因を調査します。

参照したサイト・書籍

  1. Difference between AppClassloader and SystemClassloader
    https://stackoverflow.com/questions/34650568/difference-between-appclassloader-and-systemclassloader

目次

  1. gradle の build タスクから実行された時には成功するが IntelliJ IDEA の「Run ‘All Tests’」から実行された時に失敗する原因とは?
  2. Spring Boot 2.0.8 に切り戻して確認してみる
  3. Spring Boot 2.1.2 に切り替えて「JUnit」の「Shorten command line」の設定を「JAR manifest」にして確認する

手順

gradle の build タスクから実行された時には成功するが IntelliJ IDEA の「Run ‘All Tests’」から実行された時に失敗する原因とは?

まずは状況をまとめてみます。失敗する ksbysample.webapp.lending.SampleHelperTest$異常処理のテスト.SampleHelper_encryptを呼ぶとRuntimeExceptionをthrowする テストメソッドは以下の実装になっており、

  • ksbysample.webapp.lending.SampleHelperTest テストクラスは groovy で書かれている。
  • SampleHelperTest クラスの中に Spock で書かれたテストクラス「正常処理のテスト」と JUnit4 で書かれたテストクラス「異常処理のテスト」が混在している。SampleHelperTest クラスには @RunWith(Enclosed) アノテーションが付与されている。
  • JUnit4 で書かれたテストクラス「異常処理のテスト」は PowerMock を使用して static メソッドのテストをしている。

エラーメッセージ Caused by: java.lang.NullPointerException at org.thymeleaf.spring5.util.SpringVersionUtils.<clinit>(SpringVersionUtils.java:52) からエラーの発生箇所を確認すると、以下の赤枠の部分で NullPointerException が発生しています。

f:id:ksby:20190213003906p:plain

赤枠の行に breakpoint を設定してテストを debug 実行してみると、

f:id:ksby:20190213004948p:plain

breakpoint で処理が止まり、Debug Tool Window の Variables に「SpringVersion.class」と「SpringVersion.class.getPackage()」を表示させてみると以下の結果でした。

f:id:ksby:20190213010416p:plain

  • SpringVersion.class はインスタンスが生成されている。classLoader は JavassitMockClassLoader で、parent が Launcher$AppClassLoader。
  • SpringVersion.class.getPackage() は null だったので、SpringVersion.class.getPackage().getName() を呼べば NullPointerException が発生する。

今度は breakpoint はそのままで、gradle の build タスクを debug 実行してみます(Gradle Tool Window に表示されている build タスクでコンテキストメニューを表示して「Debug ...」メニューを選択します)。

f:id:ksby:20190213011406p:plain

普段は表示されない Connected to the target VM, address: '127.0.0.1:56365', transport: 'socket'Connected to the VM started by ':test' (localhost:56488). Open the debugger session tab のメッセージが表示されて test タスクが実行された後、

f:id:ksby:20190213011945p:plain

breakpoint で処理が止まるので、Debug Tool Window の Variables に「SpringVersion.class」と「SpringVersion.class.getPackage()」を表示させてみると以下の結果でした。

f:id:ksby:20190213011849p:plain

  • SpringVersion.class はインスタンスが生成されている。classLoader は Launcher$AppClassLoader。
  • SpringVersion.class.getPackage() は null ではなく package 名が返ってきている。

「Run ‘All Tests’」からテストを実行した時は org.thymeleaf.spring5.util.SpringVersionUtils の classLoader が JavassitMockClassLoader(おそらく https://github.com/powermock/powermock/blob/release/2.x/powermock-core/src/main/java/org/powermock/core/classloader/javassist/JavassistMockClassLoader.java)になっていてい Mock 化されており SpringVersion.class.getPackage() が null を返すため、ということでしょうか。。。?

Spring Boot 2.0.8 に切り戻して確認してみる

Spring Boot のバージョンを 2.1.2 に上げる前は ksbysample.webapp.lending.SampleHelperTest$異常処理のテスト.SampleHelper_encryptを呼ぶとRuntimeExceptionをthrowする テストメソッドは成功していたはずなので、一旦 master ブランチに切り替えて確認してみます。

master ブランチに切り替えてから Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新した後、テストを実行すると 2.1.2 にバージョンアップした後と同じ理由で失敗しました。

f:id:ksby:20190214031645p:plain

以前は成功していたはずなのになぜ?。。。と思ったのですが、よく考えたら Spring Boot 2.0.x の Web アプリを 2.1.x へバージョンアップする ( その4 )( テストが大量に失敗する原因を解消する ) の時に「Run/Debug Configurations」の画面で「JUnit」の「Shorten command line」の設定を「classpath file」に変更していたので、それが原因かもしれません。元の「user-local default: none」の設定に戻してみます。

f:id:ksby:20190214032155p:plain

再度テストを実行すると今度は成功しました。どうも「classpath file」の設定だと PowerMock を使用したテストで失敗することがあるという結論のようです。

f:id:ksby:20190214032400p:plain

ただし「user-local default: none」の設定でテストが成功することが分かっても、この設定では Spring Boot を 2.1.2 に上げた時に Command line is too long. のメッセージが表示されてテストが実行できません。「Shorten command line」の設定には「JAR manifest」もあるので、そちらに設定を変更して試してみます。

f:id:ksby:20190214032858p:plain

テストを実行すると成功しました。

f:id:ksby:20190214033058p:plain

ということで ksbysample.webapp.lending.SampleHelperTest$異常処理のテスト.SampleHelper_encryptを呼ぶとRuntimeExceptionをthrowする テストメソッドが失敗したのは「Run/Debug Configurations」の画面で「JUnit」の「Shorten command line」の設定を「classpath file」にしたからという結論でした。「JAR manifest」にすると成功するので、今度はこの設定で Spring Boot 2.1.2 に上げた状態に切り替えて試してみます。

Spring Boot 2.1.2 に切り替えて「JUnit」の「Shorten command line」の設定を「JAR manifest」にして確認する

Spring Boot 2.1.2 へのバージョンアップの作業をしているブランチに切り替えてから Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新した後、テストを実行してみると今度は成功しました。

f:id:ksby:20190214034316p:plain

Project Tool Window の src/test から「Run ‘All Tests’」を実行すると今度は全てのテストが成功しました!

f:id:ksby:20190214034910p:plain

「Shorten command line」の設定は「classpath file」の方が良さそうに思えたのですが「JAR manifest」の方が良かったようです。ただし PowerMock のテストでしかエラーになっていないので、普通はどちらでも良いのかもしれません。

履歴

2019/02/14
初版発行。