JUnit4 から JUnit5 へ移行する方法
ここ最近 JUnit4 から JUnit5 に移行する方法を模索していたのですが, あっちへこっちへ資料を捜索して大変だったのでまとめることにしました
移行の進め方の方針
小さなプロジェクトだと, さっくり JUnit4 から JUnit5 へ記法を一気に変えることもできるかなと思うのですが, 大きいプロジェクトになると物量が厳しいので, 基本的には以下のように進めるのが安定かなと思ってます。
- JUnit のライブラリを更新する
JUnit4 を削除し, JUnit5 を追加する(※ ただし, JUnit4 実行を維持するために, junit-vintage-engine なるものも入れる) - JUnit4 記法の既存のテストを JUnit5 の記法に書き換える
(新しいテストは JUnit5 の記法で書いてく) - 全てのテストが JUnit5 の記法になったら, junit-vintage-engine を依存から削除する
上記 2 番目は, junit-vintage-engine が依存ライブラリに入っているかぎりやらなくても動作するけど, いずれやらねばならぬときは来るのではと思うので, 好きなタイミングで。
ちなみに JUnit の公式の移行方法は → https://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4
具体的な進め方
ライブラリの更新
まずは, JUnit5 のライブラリに切り替えます(以下は Maven なんですが, Gradle の場合は適当によみかえてください[1])
もともと JUnit4 で以下のような感じで書いていると思うので
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
上記の内容の代わりに以下をいれます
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
ちなみに僕は InteliJ 使ってるので以下もいれました
(ただ公式のメッセージ Only needed to run tests in a version of IntelliJ IDEA that bundles older versions
をみると, 新しい InteliJ だったら入れなくても良いのかなとおもいました。
<!-- Only needed to run tests in a version of IntelliJ IDEA that bundles older versions -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.7.0</version>
<scope>test</scope>
</dependency>
ちなみにそれぞれの意味は, 公式の説明 を参考に以下な感じです。必要になったときに, 他のものも入れていく感じですね。
<dl>
<dt>org.junit.jupiter の junit-jupiter-engine</dt>
<dd>JUnit Jupiter のテストエンジン(実行時)</dd>
<dt>org.junit.jupiter の junit-jupiter-api</dt>
<dd>テスト記述のための各種 API(@Test
とか @BeforeEach
とか。詳細は公式へ)</dd>
<dt>org.junit.vintage の junit-vintage-engine</dt>
<dd>JUnit3 と JUnit4 を実行可能にするテストエンジン</dd>
</dl>
JUnit4 → JUnit5 へのテストの書き換え
個人的には JUnit4 の @Rule
がなくなったのが, なかなかでした
単純な書き換え 1 (Package 変更)
JUnit4 で org.junit
配下のものを使っていたのを, JUnit5 では org.junit.jupiter.api
配下のものになるので単純に import の Package を書き換えます
対象 | JUnit4 Package | JUnit5 Package |
---|---|---|
@Test とか |
org.junit.* | org.junit.jupiter.api.* |
fail() とか |
org.junit.Assert.* | org.junit.jupiter.api.Assertions.* |
単純な書き換え 2 (アノテーションの置換)
アノテーションが変更になっているので, 置き換えます
JUnit4 | JUnit5 |
---|---|
@Before |
@BeforeEach |
@After |
@AfterEach |
@BeforeClass |
@BeforeAll |
@AfterClass |
@AfterAll |
@Ignore |
@Disabled |
@Category |
@Tag |
※ ただし, テストの階層化などで inner class で @BeforeClass や @AfterClass してる場合は, もう少しアノテーションが入用なので, 別途下の方にかきます) |
なお, 単純な書き換えでは済まないやつは, 以下とかです
-
@RunWith
から@ExtendWith
への切り替え -
@Rule
や@ClassRule
から,@ExtendWith
や@RegisterExtension
への切り替え
テストの階層化の方法の変更
JUnit4 ではテストの整理には inner class と @RunWith(Enclosed.class)
を使ってたかと思いますが(以下みたいな)
@RunWith(Enclosed.class)
public class SampleJUnit4Test {
public static class TestCategoryA {
@Test
public void testA1() { ... }
}
public static class TestCategoryB {
@Test
public void testB1() { ... }
}
}
JUnit5 では @Nested
と static でないクラスを用いて階層化ができます。
記法が素直になったかなと思いました(所感)。
public class SampleJUnit5Test {
@Nested
class TestCategoryA {
@Test
public void testA1() { ... }
}
@Nested
class TestCategoryB {
@Test
public void testB1() { ... }
}
}
注意事項としては, JUnit4 から JUnit5 への移行作業中に @Nested
をつけたものの static class にしたままだと, コンパイルエラーにはならないのですがテスト実行が無視される事象が発生するのでご注意ください。(詳細は調べてないです)
inner class で BeforeAll, AfterAll をしている箇所の対応
手前で, 以下のように書いていたかと思いますが,
※ ただし, テストの階層化などで inner class で @BeforeClass や @AfterClass してる場合は, もう少しアノテーションが入用なので, 別途下の方にかきます)
JUnit5 の移行時に, @Nested
を用いたテスト階層化にすることで, inner class が static ではなくなります。このため, inner class で @BeforeClass
や @AfterClass
をしている場合はエラーになります。
この場合は, 公式記載の内容に従って, @TestInstance(Lifecycle.PER_CLASS)
を対象のクラスにつけてあげると, テストインスタンスがテストクラスごとに作成されるようになり, inner class で @BeforeAll
, @AfterAll
が使えるようになります。
public class SampleJUnit5Test {
@Nested
@TestInstance(Lifecycle.PER_CLASS)
class TestCategory {
@BeforeAll
static void beforeAll() { ... }
}
}
Exception の Assert 方法の変更
Exception 系の Assert 方法が変更になったのでそこも変えます。
JUnit4 だと, @Test(expected = SampleException.class)
や, ExpectedException
を使ってたかなと思います。
@Test(expected = NullPointerException.class)
public void NullPointerExceptionが発生する() {
/* NullPointerException の発生を期待するテスト */
}
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Test
public void SampleExceptionが発生する() {
expectedException.expect(SampleException.class);
expectedException.expectMessage("SampleException のメッセージ");
/* SampleException の発生を期待するテスト */
}
JUnit5 の場合は assertThrows
を使います。
assetThrows
の引数に, 発生を期待する Exception の class と, テストコード(lambda 式経由)を渡します。
@Test
public void NullPointerExceptionが発生する() {
assertThrows(NullPointerException.class, () -> {
/* NullPointerException の発生を期待するテスト */
});
}
例外の発生チェックのみであれば assertThrows
のみで大丈夫ですが, エラーメッセージ含み発生した Exception の詳細をテストする場合は, assertThrows
が返す Exception を受け取って諸々の検証を書きます。
@Test
public void SampleExceptionが発生する() {
SampleException exception = assertThrows(SampleException.class, () -> {
/* SampleException の発生を期待するテスト */
});
assertEquals("SampleException のメッセージ", exception.getMessage());
}
TemporaryFolder を用いた試験の書き方の変更
JUnit4 ではファイル周りの試験などのために @Rule
+ TemporaryFolder
を使って試験を書いてる箇所があるかなと思います。JUnit5 では @Rule
がいなくなったのでこちらも修正対象です。
といっても非常に単純で, JUnit4 で以下のように書かれていた内容を
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private Path root;
@Before
public void setup() {
root = temporaryFolder.getRoot().toPath().toAbsolutePath();
}
以下の感じに直す(なお, @TempDir
がつく変数は private
だとエラーになるので注意)
@TempDir
Path root;
これ, かなり煩わしいものが消えて良い感じになったきがします。
そのほか
- データセットに対する繰り返し試験(JUnit4 は
@RunWith(Theories.class)
+@DataPoint
+@Theory
だったもの)- JUnit5 では: 繰り返し対象がプリミティブ型の場合は,
@ParameterizedTest
+@ValueSource
- JUnit5 では: 繰り返し対象がオブジェクトの場合は,
@ParameterizedTest
+@MethodSource
- JUnit5 では: 繰り返し対象がプリミティブ型の場合は,
-
Gradle の設定は, 公式の https://github.com/junit-team/junit5-samples/tree/main/junit5-migration-gradle が参考になるかと ↩︎
Discussion