Javaのリフレクションは、JUnitの「カバレッジ」>「JUnit テスト」と「実行」>「JUnit テスト」で使用されるメソッドが異なるらしい...

f:id:ts0818:20190615110907j:plain

ポケットのなかにはビスケットがひとつ
ポケットをたたくとビスケットはふたつ
もひとつたたくとビスケットはみっつ
たたいてみるたびビスケットはふえる
そんなふしぎなポケットがほしい  そんなふしぎなポケットがほしい

まど・みちお - Wikipedia

ポケットを叩くと、お金が増える、そんな不思議なポケットが欲しい~♪

という...はい、どうもボクです。

というわけで、今回もJavaのお話です。 

そして、

www.yutorism.jp

⇧  上記サイト様にありますように、長時間労働で成長するわけじゃないのね(涙)。とりあえず、次の会社では、もう少し、成長できるように頑張ろうと思う今日この頃です。研修は1ヵ月だけみたいだけど、現場にはチームで配属って言ってたし、即戦力でなくとも大丈夫って仰っていたし、多少は気が休まるのかな~と。

そんでは、レッツトライ~。

 

JavaのリフレクションとJunitのコラボの罠

というか、こんな現象が起こりえるんだという事実に震撼しました。ことの発端は、職場の同僚の方が、単体テスト してた時に、

で検証結果が異なるという事態に直面。っていうか検証ツールで、実行結果が異なるってアカン気がするけど...JUnitさん。

 

結論から言ってしまうと、タイトルでも言った通り、「カバレッジ実行」と「実行」で使用されてるメソッドが異なっていたらしく、リフレクションでメソッドを実行した時に発覚したという。

つまり、リフレクションの使用条件とJUnitの組み合わせが生み出した悲劇(涙)。まぁ、Eclipseプラグインが影響していたんですけどね...。

 

というわけで、再現してみる

実際に、どういうことが起こっていたのか、「百聞は一見に如かず」ということで、見た方が納得できるのではなかろうかということで、試してみますか。

JUnit は、 Javaの標準APIではないので、外部からインストールする必要がありますんで、今回は、Eclipseで、Gradleプロジェクト作成で。(最近のEclipseはデフォルトでJUnitが入っているようでした...)

⇩ ちなみに、ビルドツールは、「Gradle」が人気なんですかね?

http://sassembla.github.io/Public/12:05:27%2018-03-20/12:05:27%2018-03-20.html

 

話が脱線しましたが、Eclipse を起動し、「ファイル(F)」>「Gradle プロジェクト」で。

f:id:ts0818:20190615123604p:plain

f:id:ts0818:20190615134453p:plain

f:id:ts0818:20190615134536p:plain

f:id:ts0818:20190615134611p:plain

パースペクティブ」を「Java EE」にしてますが、Gradleプロジェクトができました。

f:id:ts0818:20190615134300p:plain

「プロジェクトと外部の依存関係」に「junit-×.××.jar」ってあれば、JUnitが使えますかね。

parasoft.techmatrix.jp

EclipseIntelliJなどの一般的なIDEでは、デフォルトでJUnit機能がインストールされています。IDEを使用せず、MavenやGradleなどのビルドシステムのみを使用している場合、Junit 4/5のインストールはそれぞれpom.xmlまたはbuild.gradleによって処理されます。Junit 5は3つのモジュールに分割されていますが、そのうちの1つは、Junit 4および3のアノテーション/構文をサポートする由緒あるモジュールです(ただし、Junit 3の使用は率直に言って悲惨であり、通常、かなり古いプロジェクトでしか見られません)。

JUnitチュートリアル:Javaユニットテストのセットアップ、作成、実行 – Parasoft 

f:id:ts0818:20190615141401p:plain

だが、しかし!

www.ibm.com

対象のプロジェクトでコード・カバレッジを使用可能にします。 対象のプロジェクトを 右クリックし、「プロパティー > 「コード・カバレッジを 選択します。 「コード・カバレッジを使用可能にする (Enable code coverage)」を選択します。 「OK」をクリックします。

IBM Knowledge Center

Eclipse側で設定を有効にする必要があるらしい。

プロジェクトを選択した状態で、「プロパティー(R)」で。

f:id:ts0818:20190615175610p:plain

ん?コードカバレッジが存在しないんだが...

f:id:ts0818:20190615175852p:plain

 

summer-wars.hatenadiary.jp

⇧  「EclEmma」ってEclipseプラグインが必要だったようです...知らんがな(涙)

 

というわけで、インストール...しようと思ったら、「マーケットプレイス」が選択肢として表示されないんですけど...。

f:id:ts0818:20190615181117p:plain

 

ex1.m-yabe.com

⇧  上記サイト様を参考にさせていただきました。

download.eclipse.org

⇧  上記でご自分のお使いのEclipseのバージョンのURLをコピーします。自分の場合は、「http://download.eclipse.org/mpc/2018-09/?d」ってなってたので、「http://download.eclipse.org/mpc/2018-09」に整えました。

f:id:ts0818:20190615181908p:plain

Eclipseのバージョンとかは、「ヘルプ(H)」>「Eclipse IDE について(A)」で。

f:id:ts0818:20190615182002p:plain

f:id:ts0818:20190615182133p:plain

というわけで、「ヘルプ(H)」>「新規ソフトウェアのインストール...」で。

「対象作業(W):」にコピペしておいたURLを張り付け、「追加(A)...」で。

f:id:ts0818:20190615181731p:plain

「名前(N):」を適当に入力し、「追加(D)」で。

f:id:ts0818:20190615183935p:plain

「EPPマーケットプレイス・クライアント」にチェックし、「次へ(N)>」。

f:id:ts0818:20190615182739p:plain

なんか、上手くいってなさそうだけど...インストールしないと「マーケットプレイス」使えないんでね。「次へ(N)>」。

f:id:ts0818:20190615190535p:plain

「次へ(N)>」。

f:id:ts0818:20190615190706p:plain

「完了(F)」で。

f:id:ts0818:20190615190742p:plain

再起動で。

f:id:ts0818:20190615191041p:plain

Eclipse マーケットプレイス(M)」が復活しました。

f:id:ts0818:20190615193642p:plain

「検索(I):」で、「EclEmma」で。表示された「EclEmma Java コード・カバレッジ」を「インストール」で。

f:id:ts0818:20190615193831p:plain

「確認(C)>」で。

f:id:ts0818:20190615194050p:plain

「完了(F)>」で。

f:id:ts0818:20190615194119p:plain

再起動で。

f:id:ts0818:20190615194224p:plain

カバレッジ(V)」>「JUnitテスト」が選択できるようになりました。

f:id:ts0818:20190615200313p:plain



そんでは、デフォルトで用意されている、「Library.class」に実際の処理を、「LibraryTest.class」にテストコードを記述します。それとは別に、今回、もう1つクラスを追加します。

GitHub にソースを上げたので、宜しければご参照ください。

github.com

プロジェクト構成は、こんな感じ。

「プロジェクトと外部の依存関係」には、「junit-x.xx.jar」「hamcrest-core-x.x.jar」があれば問題ないかと。

f:id:ts0818:20190615223022p:plain

んで、「カバレッジ(V)」>「JUnitテスト」で実行。注意する点としては、テストクラス(「src/test/java」のほう)で実行するってとこですかね。

f:id:ts0818:20190615223239p:plain

んで、「コンソール」には、

private static boolean[] dto.Jyugemu.$jacocoInit()
public void dto.Jyugemu.setName03(java.lang.String)
public java.lang.String dto.Jyugemu.getName02()
public java.lang.String dto.Jyugemu.getName05()
public void dto.Jyugemu.setName10(java.lang.String)
public java.lang.String dto.Jyugemu.getName08()
public void dto.Jyugemu.setName11(java.lang.String)
public java.lang.String dto.Jyugemu.getName11()
public void dto.Jyugemu.setName05(java.lang.String)
public java.lang.String dto.Jyugemu.getName01()
public java.lang.String dto.Jyugemu.getName03()
public java.lang.String dto.Jyugemu.getName06()
public void dto.Jyugemu.setName07(java.lang.String)
public void dto.Jyugemu.setName08(java.lang.String)
public void dto.Jyugemu.setName01(java.lang.String)
public java.lang.String dto.Jyugemu.getName09()
public java.lang.String dto.Jyugemu.getName10()
public void dto.Jyugemu.setName09(java.lang.String)
public void dto.Jyugemu.setName02(java.lang.String)
public java.lang.String dto.Jyugemu.getName04()
public void dto.Jyugemu.setName04(java.lang.String)
public void dto.Jyugemu.setName06(java.lang.String)
public java.lang.String dto.Jyugemu.getName07()
----- 結果 -----
setName01=寿限無
setName02=寿限無
setName03=五劫ごこうの擦すり切きれ
setName04=海砂利かいじゃり水魚すいぎょの
setName05=水行末すいぎょうまつ・雲来末うんらいまつ・風来末ふうらいまつ
setName06=喰う寝る処ところに住む処
setName07=藪やぶら柑子こうじの藪柑子
setName08=パイポパイポパイポシューリンガン
setName09=シューリンガングーリンダイ
setName10=グーリンダイポンポコピーポンポコナーの
setName11=長久命ちょうきゅうめいの長助

んでは、「実行」>「JUnitテスト」で実行では。

f:id:ts0818:20190615224153p:plain

public java.lang.String dto.Jyugemu.getName07()
public void dto.Jyugemu.setName08(java.lang.String)
public java.lang.String dto.Jyugemu.getName09()
public void dto.Jyugemu.setName09(java.lang.String)
public java.lang.String dto.Jyugemu.getName10()
public java.lang.String dto.Jyugemu.getName08()
public java.lang.String dto.Jyugemu.getName06()
public void dto.Jyugemu.setName06(java.lang.String)
public void dto.Jyugemu.setName07(java.lang.String)
public void dto.Jyugemu.setName10(java.lang.String)
public java.lang.String dto.Jyugemu.getName11()
public void dto.Jyugemu.setName11(java.lang.String)
public java.lang.String dto.Jyugemu.getName01()
public java.lang.String dto.Jyugemu.getName02()
public void dto.Jyugemu.setName02(java.lang.String)
public java.lang.String dto.Jyugemu.getName03()
public void dto.Jyugemu.setName03(java.lang.String)
public void dto.Jyugemu.setName04(java.lang.String)
public java.lang.String dto.Jyugemu.getName05()
public void dto.Jyugemu.setName05(java.lang.String)
public void dto.Jyugemu.setName01(java.lang.String)
public java.lang.String dto.Jyugemu.getName04()
----- 結果 -----
setName01=寿限無
setName02=寿限無
setName03=五劫ごこうの擦すり切きれ
setName04=海砂利かいじゃり水魚すいぎょの
setName05=水行末すいぎょうまつ・雲来末うんらいまつ・風来末ふうらいまつ
setName06=喰う寝る処ところに住む処
setName07=藪やぶら柑子こうじの藪柑子
setName08=パイポパイポパイポシューリンガン
setName09=シューリンガングーリンダイ
setName10=グーリンダイポンポコピーポンポコナーの
setName11=長久命ちょうきゅうめいの長助

と。

おわかりいただけただろうか...

何故かは分からんのですが、『private static boolean[] dto.Jyugemu.$jacocoInit()』っていう奴が、「カバレッジ(V)」>「JUnitテスト」では、Jyugemuクラスのメソッドに追加されてしまっているのである!

何これ~、怖い~!

おそらく、カバレッジを実行するためのEclipseプラグイン「EclEmma」が影響しているのであろうと。

信じるか信じないかは、貴方次第~。

なので、リフレクションを使用しているクラスの単体テストにはお気をつけあそばせ。

今回はこのへんで。