JUnit 5 でどこが変わったのか
JUnit5 でどこが変わったのか、今いるチームの開発メンバや、JUnit5ざっくり知りたい人向けに書いておきます。 この記事では以下の内容について書いていきます。
- なぜ移行するか
- どこが変わったか
- まとめ
なぜ移行するのか
なぜ移行するかを簡単に説明しておきます。 以下のような内容になってくるかなと思います。
- やっぱり新しいライブラリ・ツールを使いたい
- 新しい
@ParameterizedTest
を使いたい - Java 11のサポート
僕としては、これから何年かはJUnit5で書いていくことになると思うので、ツールを早く導入していきたいという気持ちがあり
「やっぱり新しいライブラリ・ツールを使いたい」という理由を挙げています。
また、junit-jupiter-params
の @ParameterizedTest
を使いたい気持ちがあります。まだAPIとしては experimental ですが、
個人的には、以前のものと比べると、使いやすいなぁと思っています。
また、JUnit5はモジュールの構成が拡張のしやすさもあり、今後使っていきたいところです。
JUnit5はJava11のサポートがありますが、JUnit4では動く保証がありません。 そのため、Java11へ移行するのであれば、JUnit5へ移行していく必要があります。 とはいいつつ、JUnit4は今の所、新しいJavaのバージョンになっても、手元のアプリケーションでは、なんとなく動いています。 Javaのアップデートのために急ぐ必要はないと思っています。 また、JUnit5にはJUnit4で書かれたテストを動かす機能もあるので、テストコードの修正なしに Gradleなどのビルドツールの設定と依存関係を追加することによって Java11への移行がすんなり行えるかもしれません。
そんなこんなで、今すぐ変えよう!という感じではないのですが、やっぱり新しいものは使っていきたいね、というところで 「どこが変わったのか」について書いていきたいと思います。 「どうやって移行するのか」については、この記事を読んだあとに以下の記事を読んでいただければな、と思います。
どこが変わったのか
まずはざっくり、マイグレーションガイドを見つつ、自分が知っている変わった点の一覧を書いてみます。
- アノテーションのパッケージ名とアノテーション自体の名前の変更
@Test
にあったexpected
やtimeout
の属性の削除- アサーションのパッケージ名変更とインターフェースの変更
- 新しい拡張モデルとして、Extensionの追加
- Extensionの追加に伴う、Rule, ClassRuleの廃止
- テストコードに
public
な修飾子をつけなくてもよくなった - 4 --> 5 のマイグレーション用のモジュールの追加
- JUnit 5向けのテストをJUnit4で動かすためのモジュール
Enclosed
やParameterizedTest
が モダンな感じになった- JUnit5を動かすために、Gradleへの設定が必要
- Java9 以降のJDKのバージョンへのサポート
色々ありますが、ドキュメントにも書かれていますが、 日本語の情報だと、irofさんという方の「どうしよう JUnit5」というスライドに大体書かれています。
この記事では、JUnit5と書いていますが、JUnit5ってなんなのかを表す言葉をユーザガイドから引用すると
になります。
JUnit Platformというテストを動かすためのAPIや実装で JUnit Jupiterは JUnitの5として新しいプログラミングモデルと拡張モデルのAPIとPlatformで動かすためのEngineの実装が含まれています。 JUnit Vintageは JUnit Platform上で JUnit4のプログラミングモデルをPlatformで動かすためのEngineの実装です。 また、JUnit PlatformにはIDEやビルドツールへの対応も含まれており、開発者は、自分達でEngineを書いて 好きな環境で動かすことも可能になっています。僕の知っているEngineの実装としては jmockitというライブラリがあるのですが、その中にEngineの実装があります。 余談ですが、元々jmockitoはJUnit4のクラスにモンキーパッチを当てていましたが 一段抽象化が入ることにより、ダーティなハックをすることなく、強力なモックを使うことが出来るようになっていそうです。 実際使ってことないのでわかりませんがw
とまぁ、本題からずれてしまったのですが、より1段階抽象化されており JUnit4と5が共存出来る形になっています。
アノテーションのパッケージ名とアノテーション自体の名前の変更
アノテーション周りが org.junit
から org.junit.jupiter.api
に変更されています。
また、@Ignore
が @Disable
になっていたりと
細かい変更はありますが、基本的にはパッケージの移動が行われています。
また、@Test
にあった expected
や timeout
の属性の削除なども行われており
こちらは、アサーションによって、テストするように変更されています。
どう書き換えていくかというと、以下のようなイメージです。
- @Test(expected = HogeException.class) + @Test public void testThrows() { - foo() + Exception e = assertThrows(HogeException.class, () -> foo()); + assertEquals("Hoge", e.getMessage()); }
timeoutについては省略しますが、assertTimeoutというアサーションが junit-jupiter
に追加されています。
そちらを使って書き換えることになるでしょう。
アサーションのパッケージ名変更とインターフェースの変更
org.junit.jupiter.api.Assertions
にアサーションが移動しています。
また、assertThat
の廃止がされています。
加えて、
新しい拡張モデルとして、Extensionの追加
Extensionという新しいインターフェースが追加されています。 JUnit4でいうSpringRunnerだと、SpringのチームからSpringExtensionが追加されていたり MockitoのチームからはMockitoExtensionが提供されています。 順次サポートが広がってきており、エコシステムも充実して使える状態になってきています。
加えて、Extensionの追加に伴う、Rule, ClassRuleが廃止されています。 この辺は、自分で書いている場合は自分で移行したり、ライブラリの開発チームから提供されるのを待つ必要があります。 ただ、Extensionになって、簡単に書けるようになっているので 自分で実装を追加して、ライブラリにコントリビュートしても良いかもしれないですね
Extensionについては以下のUserGuideを見てほしいのですが、RunWithと違う点を一つ書いておきます。
JUnit4では、RunWithでは一つしか指定できなかった拡張モデルを、Extensionでは複数指定できるようになっています。
@ExtendWith(DatabaseExtension.class) @ExtendWith(WebServerExtension.class) class MySecondTests { // ... }
また、JUnit5からは全体的に合成アノテーションのサポートがされており
以下のような @LargeTest
のようにアノテーションをまとめる事ができます。
この辺はSpringをがっつり使っている方には馴染み深いものかと思います。
ドキュメント的にはComposed Annotationとして紹介されています。
ドキュメントには以下のリンク先に書かれています。
@ExtendWith(DatabaseExtension.class) @ExtendWith(WebServerExtension.class) // その他必要なアノテーションを省略 @interface LargeTest {} // DatebaseExtensionとWebServerExtensionをまとめるアノテーション @LargeTest class MySecondTests { // ... }
このExtensionのモデルは非常に拡張しやすい形になっているのですが
Rule, ClassRuleで出来ていたテストコード側で状態を持つような拡張がしづらくなったかなぁと思っています。
追記: 2019/07/03 この点は RegisterExtension (JUnit 5.1.1 API) を使えば良さそう
どんなコードかというと、WireMockのRuleがそれに当たります。
@Rule WireMockRule wireMock = new WireMockRule(WireMockConfiguration.wireMockConfig().dynamicPort());
これを移行する方法を考えた時にどうするか、というと Extensionの定義に加えて、Extensionに設定を伝えるアノテーションやルールが必要になってきます。 wiremock本体ではないですが、コミュニティから出ている、wiremock-extensionであれば、以下のような形に書き換えることになります。
@Managed WireMockServer wireMock = new WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort());
という風に、いくつか不便になりましたが、 Extension自体が簡単に書きやすくなった、というのもあるので トレードオフという感じですね。
これからJUnit5向けにExtensionが出てくると思うので 移行期間はJUnit4で動かすのか、JUnit5で頑張って動かすのか考えつつ 移行していきたいですね。
テストコードに public
な修飾子をつけなくてもよくなった
地味な変更です。
テストメソッドやテストクラスに public
をつける必要がありましたが、必要ありません。
テストコードのノイズが減って良いですね。
- public class FooServiceTest { + class FooServiceTest { @Test - public void test() { + void test() { // ... } }
変えなくても動くので、古いコードについては別にそのままで良いかなぁと思います。 新しいテストについては消していきたい気がしますが、好みのレベルと思います。
4 --> 5 のMigration用のモジュールの追加
いくつか、マイグレーション用のモジュールがあります。
- JUnit5でJUnit4のテストを動かすための
JUnit Vintage
- JUnit4でJUnit5のテストを動かすための
@RunWith(JUnitPlatform.class)
- JUnit5でJUnit4の機能を一部再現するための
junit-jupiter-migrationsupport
といったように、マイグレーションをサポートするモジュールや段階的に切り替えていくためのモジュールがたくさん用意されています。 規模が大きいのであれば、これらを駆使して移行していきたいですね。
Enclosed
や ParameterizedTest
が モダンな感じになった
Enclosed
という 実はExperimentalだったアノテーションがあるのですが
このアノテーションを使ってテストを構造化すると、以下のように少し複雑な形になります。
public
や static
が必要なのもあってかちょっと冗長に見えます。
@RunWith(Enclosed.class) public class OuterTest { public static class InnerTest { } }
Nested
という追加されたアノテーションを使って構造化すると以下のようになります。
class OuterTest { @Nested class InnerTest { } }
修飾子が要らなくなったのも合わせて対応してみると、非常に簡潔に構造を示すことが出来ます。
ただ、Enclosed
と比べて注意する点が一つあります。
それは InnerClass
に static
をつけていると動きません。
移行時に注意しておきたいですね。
@ParameterizedTest
について書こうと思ったんですが
ドキュメント見ろ、で終わる話なので詳しく書きませんが
非常に便利になったと思います。
以前書こうと思ったら、残念すぎて心が折れた記憶があります。
JUnit4でも、サードパーティのモジュールに JUnitParams
というのがありますが
そっちを使えば、良い感じに書けるそうですが
やっぱり、公式でモダンな形でサポートされているのは強いですね。
詳しくは下のリンクを見て下さい
JUnit5を動かすために、Gradleへの設定が必要
Gradleのデフォルトのテストでは、JUnit4で動くようになっており GradleでJUnit5を動かすには設定の追加が必要です。 ただ、新しいプロジェクトでは以下のように、簡単に設定が出来ます。
test { useJUnitPlatform() }
もちろん、Jacocoの対応もばっちりです。 Gradle 4.6から使えるようになっているので、Gradleのアップデートがまだな方はアップデートしていきましょう。
移行期間に伴い、JUnit4で動かしたい場合は、JUnit Vintageを使う必要があります。 以下にような形で設定を追加すれば良いでしょう。
test { useJUnitPlatform { includeEngines 'junit-jupiter', 'junit-vintage' } }
詳しくは以下のリンク先を見てください
まとめ
今回の記事では、ざっくり知っている範囲について、メモとして書きました。 JUnit5のプログラミングモデル どうやって移行したか、については以前書いた記事をご覧ください。
以前書いた記事では、全部書き換える手法を取りましたが、成長したプロダクトでは 一気に書き換えるとPRのサイズがめちゃくちゃでかくなります。 上の記事で書いた、移行したプロジェクトのサイズは、そこそこ小さかったはずですが、+-共に2000行程度になり、4000行程度の差分が生まれました。 (僕は、同僚氏にでかいPRを投げつけました、の札。(画像略)) そのため、本来はゆっくり移行すべきかと思います。
また、前回の記事から、もう一つSpring Bootで書かれたサーバのプロジェクトを移行しましたが 大体似たような移行方法になったので、記事にはしないことにしました。
今度はJUnit Vintageを使って段階的に移行する方法を取ってみて、記事を書いてみようかなと思います。