内部で使っているライブラリを JUnit 5 (jupiter) に 移行した
この記事では、JUnit5への移行を行うと共に どういう書き換えをしたかを書いておきます。
以下の内容で書いていきます。
- 今回移行したプロジェクトの前提
- どういう方法で移行するか
- PMDのアップデート
- junit 4系の依存を完全に外す
- junit-jupiterの依存とテストの設定を追加
- アノテーション周りをsedで置換
@Test(expected = HogeException.class)
を書き換える- アサーション周りをAssertJで書き直す
- Wiremockの移行
- Mockito周りの書き換え
- まとめ
今回移行したプロジェクトの前提
@Category
を使ってない@AfterClass
,@BeforeClass
を使ってない@RunWith(Enclosed.class)
を使ってない- アサーションは基本的にAssertJを使っている
- Gradleでビルドされている小さいライブラリ
- 自分達でRuleは書いてない
- SpringやMockitoのバージョンは最新に維持されていること
どういう方法で移行するか
今回は小さいプロジェクトの移行だったので、ガッとやりました。 本当はもうちょっとスモールステップで切り替えた方が良いのかなぁと思いつつ そんな規模のプロジェクトだと、色々ありそうで険しそうだなぁって気持ちになりました。
- PMDのアップデート
- junit 4系の依存を完全に外す
- junit-jupiterの依存とテストの設定を追加
- アノテーション周りをsedで置換
@Test(expected = HogeException.class)
を書き換える- アサーション周りをAssertJで書き直す
- Wiremockの移行
- Mockito周りの書き換え
PMDのアップデート
PMDのアップデートをしましょう。 JUnit4 向けの検査で false positiveになります。
Issueとしては以下になります。
6.7.0で対応されているので 6.7.0以降にアップデートしましょう
JUnit 4系の依存を完全に外す
Gradleを使っているので、以下の記述を追加して、JUnit 4系の依存を外します。
configurations.all { exclude group: 'junit', module: 'junit' }
なぜ外すかというとわかりやすく移行するためです。
junit-jupiterの依存とテストの設定を追加
build.gradleに、下の設定を追加してください。 ちなみに、この機能はGradle 4.6に追加されたもののようです。
- Gradle4.6 からの JUnit5 実行方法 https://mike-neck.hatenadiary.com/entry/2018/03/02/073000
test { useJUnitPlatform() }
また、junit-jupiterの依存を追加しておきます。
以下の junit-jupiter
は推移依存に junit-jupiter-params
が入っており
依存関係としてはシンプルで便利なので、おすすめです。
dependencies {
testCompile "org.junit.jupiter:junit-jupiter:5.4.2"
}
アノテーションをsedで置換
下の記事にも書かれていますが、大体のアノテーションは置き換えるだけで動きます。
- JUnit 4で消耗しているあなたに贈るJUnit 5入門 http://acro-engineer.hatenablog.com/entry/2017/12/07/120000
今回はひとまず、sedで置換します。(Macで動かしていて、gnu sedではないので必要に応じて読み替えてください) ここでは、MockitoやSpringのRunnerも同時に書き換えています。必要に応じて飛ばしてください。
git ls-files '*.java' | xargs sed -i '' 's/import org.junit.runner.RunWith/import org.junit.jupiter.api.extension.ExtendWith/g' git ls-files '*.java' | xargs sed -i '' 's/@RunWith(SpringJUnit4ClassRunner.class)/@ExtendWith(SpringExtension.class)/g' git ls-files '*.java' | xargs sed -i '' 's/@RunWith(SpringRunner.class)/@ExtendWith(SpringExtension.class)/g' git ls-files '*.java' | xargs sed -i '' 's/import org.springframework.test.context.junit4.SpringJUnit4ClassRunner/import org.springframework.test.context.junit.jupiter.SpringExtension/g' git ls-files '*.java' | xargs sed -i '' 's/import org.springframework.test.context.junit4.SpringRunner/import org.springframework.test.context.junit.jupiter.SpringExtension/g' git ls-files '*.java' | xargs sed -i '' 's/@RunWith(MockitoJUnitRunner.class)/@ExtendWith(MockitoExtension.class)/g' git ls-files '*.java' | xargs sed -i '' 's/import org.mockito.junit.MockitoJUnitRunner/import org.mockito.junit.jupiter.MockitoExtension/g' git ls-files '*.java' | xargs sed -i '' 's/import org.junit.After/import org.junit.jupiter.api.AfterEach/g' git ls-files '*.java' | xargs sed -i '' 's/@After/@AfterEach/g' git ls-files '*.java' | xargs sed -i '' 's/import org.junit.Before/import org.junit.jupiter.api.BeforeEach/g' git ls-files '*.java' | xargs sed -i '' 's/@Before/@BeforeEach/g' git ls-files '*.java' | xargs sed -i '' 's/import org.junit.Test/import org.junit.jupiter.api.Test/g' git ls-files '*.java' | xargs sed -i '' 's/@Test/@Test/g' git ls-files '*.java' | xargs sed -i '' 's/import org.junit.Ignore/import org.junit.jupiter.api.Disabled/g' git ls-files '*.java' | xargs sed -i '' 's/@Ignore/@Disabled/g'
ここでコンパイルエラーになることはあると思いますが 置いておきます。
@Test(expected = HogeException.class)
を書き換える
@Test(expected = HogeException.class)
を書き換えます。
junit-jupiterからは、アノテーションで投げられる例外の宣言ができなくなっています。
そのため、junit-jupiterのアサーションではassertThrows
もしくは、AssertJのassertThatThrownByなどを使ってください。
junit-jupiterのアサーションに関しては以下の記事が参考になると思います。
コンパイルエラーから見つけて直す形で書き換えていきました。
アサーション周りをAssertJで書き直す
JUnit4のアサーション周りを使っていたコードをひたすら直します。 コンパイルエラーになっているはずなので、Intellij IDEAで書き換えてはコンパイルを繰り返します。 この辺は頑張って書き換えました。
今回は使いませんでしたが、JUnit5のアサーションを使う場合は、 assertThatなどの引数が入れ替わっていたりするので書き換えましょう。
wiremockの移行
一番悩みました。WireMock本体からはExtensionが出されていません。 そのため、WireMockRuleを使ったテストを書き換える必要があります。 正直、BeforeEach/AfterEachでWireMockServerをstart/stopするのでも良いのですが 書いているテストの量が多い場合は難しい気がします。
今回は、3rd partyのモジュールを使うことにしました。 その理由としては、先程も書いた通り、3rd partyのモジュールを使ったとしても 自分で書き換えるのは苦ではないかな、というところで考えています。
使ったのは以下のライブラリです。
以下の形でテストコードで使っている @Rule
を書き換えると良いと思います。
- @Rule - WireMockRule wireMock = new WireMockRule(WireMockConfiguration.wireMockConfig().dynamicPort()); + @Managed + WireMockServer wireMock = new WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort());
依存関係としては以下の記述を追加しました。
repositories { // ... 省略 + maven { url "https://jitpack.io/" } // for wiremock-extension } dependencies { + testCompile("com.github.JensPiegsa:wiremock-extension:0.4.0") { + exclude module: "wiremock" + } }
Mockito周りの書き換え
依存関係として、 mockito-junit-jupiter
を 追加してください。
MockitoExtensionが別のモジュールとして追加されています。
dependencies {
testCompile "org.mockito:mockito-core:$mockitoVersion"
+ testCompile "org.mockito:mockito-junit-jupiter:$mockitoVersion"
}
テストのライフサイクルが小さくなってしまいます。そのため、いままで通っていたテストが通らなくなります。 どういうテストが通らなくなるかというと、以下のようなテストです。 一部のテストで使う mockのセットアップが UnnecessaryStubbingとして検出されるようになります。
@ExtendWith(MockitoExtension.class) public class CustomHealthIndicatorTest { @Mock Health.Builder builder; @BeforeEach public void setUp() throws Exception { when(builder.up()).thenReturn(builder); when(builder.down()).thenReturn(builder); } @Test public void testUp() throws Exception { builder.up(); // 実際にはテスト対象のメソッドを読んでいる } @Test public void testDown() throws Exception { builder.down(); // 実際にはテスト対象のメソッドを読んでいる } }
このテストの場合は、2つしかないので、それぞれをテストにインライン化するだけで済みますが 複数のテストがある場合、テストの書き方を工夫する必要があるかなと思います。
例えば、@Nested
を使って、それぞれのmockが必要なテストを分離します。
@ExtendWith(MockitoExtension.class) public class CustomHealthIndicatorTest { @Nested class Up { @BeforeEach public void setUp() { when(builder.up()).thenReturn(builder); } @Test public void testStreamExists() throws Exception { builder.up(); } } @Nested class Down { // 省略 } }
この挙動ですが、実はIssueに上がっています。 Issueとしては下のリンクのものです。
元の挙動のほうが移行する場合は楽なのですが、Issueが立っている以上、今のタイミングでは仕方ないので諦めました。 テストコードを見直す機会になっても良いと思います。
まとめ
正直今回はすぐに切り替えられました。Mockitoの1系から2系への移行よりも楽だった認識です。
今回移行したライブラリはそんな大きなモジュールではありませんでした。 そのため、すぐに移行することが出来ました。 また、Deprecatedなクラスや廃止される機能をできるだけ使わずに、基本的にアップデートした際に書き換える方針でやっていたので そもそも移行する手間が少なかった、というのも有ると思います。
皆さんも junit-jupiter で移行していきましょう。
@ParameterizedTest
が最高に気持ちいいので移行するの、オススメです。
今回はライブラリということもあり、大きなテストもなくスッとあげられましたが これから、もっと他のサーバーサイドアプリケーションに展開していきます。 他に分かったことがあれば、別途記事を書きます。
それではまた。