初めてのjunit 5 - oracle cloud...junit...
TRANSCRIPT
ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016
29
//testing /
広く利用されているJava単体テスト・フレームワークであるJUnitに、バージョン5の最初のアルファ・リリースが登場しました。長
く続いたバージョン4から、実に10年ぶりのメジャー・バージョンアップとなります。JUnit 5には、モジュール・アーキテクチャが導入されて新しくなったコードベース、新しいアノテーション・セット、サード・パーティ製ライブラリを統合できる拡張可能なモデル、アサーションでのラムダ式の利用といった特徴があります。JUnit 5の前身となったのは、JUnit Lambdaプロジェクトでした。このプロジェクトで次世代の単体テストに関する最初のアイデアが生まれました。また、2015年10月までクラウドファンディングによる資金集めも行われました。 これまで長い間、JUnitは単体テスト・フレームワークが本来あるべき姿を取り込み続けてきました。しかし、中核となる部分はほぼ従来のままであったため、進化が難しい面もありました。新バージョンでは、テストの実行と報告を行うのに十分な機能と安定性を持つAPIを提供するために、プロジェクト全体がまったく新しく書き直されています。JUnit 5で単体テストを実装するためには、最低でもJava 8が必要です。しかし、以前のバージョンのJavaで書かれたテストを実行することは可能です。本記事では、詳しい例を用いながら、JUnit 5の初期リリースの主な機能について説明します。本記事のコードはすべて、2016年2月にリリースされたJUnitのバージョン5.0.0-ALPHAに基づいています。本記事に含まれるサンプルの完全なソース・コードは、Java Magazineダウンロード・サイトから入手できます。 JUnitチームは、このフレームワークのリリース候補を2016年第3四半期にリリースすることを計画しています。JUnit 5の正式リリース前の最終段階として、マイルストーン1もリリースされる予定です。これは、Javaエコシステムにおいて非常に重要なリリースとなるでしょう。
JUnit 5を利用するためのツール設定JUnit 5の依存性定義は、MavenとGradleのいずれを利用しても行うことができます。本記事では、Mavenを使ってJUnit 5 APIの依存性定義を行います。次に示すのは、MavenでJUnit 5を使用するための設定です。
<dependency> <groupId>org.junit</groupId> <artifactId>junit5-api</artifactId> <version>5.0.0-ALPHA</version> <scope>test</scope></dependency>
JUnit 5は、推移的依存関係を提供するjunit5-apiモジュール、Open Test Alliance for the JVMにちなんで名付けられたopentest4jモジュールなど、複数のモジュールで構成されています。その目的は、テスト・ライブラリ、IDE、そしてTestNG、Hamcrest、Spock、Eclipse、Gradle、Mavenなどの多くのツールに最低限の共通基盤を提供することにあります。JUnit 5には、上記に加えて次のモジュールも含まれています。
■ junit5-api:テストを実装するためのクラスを含むAPIモジュールです。
■ junit4-engine:JUnit 4エンジンの実装で、JUnit 4ベースのテストを探して実行します。
■ junit5-engine:JUnit 5エンジンの実装モジュールで、JUnit 5ベースのテストを探して実行します。
■ junit-engine-api:テスト・エンジンの抽象APIモジュールで、現在および将来のテスト・フレームワークにテスト・エンジンを登録して統合できる、機能拡張メカニズムを提供します。テスト・エンジンはID文字列で識別され、クラス・ローダーがリフレクションを使って検索します。
MERT ÇALIS¸KAN
初めてのJUnit 5完全に再設計され、多くの便利な追加機能が搭載されたJUnit 5がついに登場
ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016
30
//testing /
テスト・エンジンは、JDKのServiceLoaderクラスを利用して自身を登録します。
■ junit-launcher:ビルド・ツールやIDEがテストを実行するために使用する統合モジュールです。JUnit 4とJUnit 5のテストを一度に実行できるようになっています。
■ junit-console:JUnit 4とJUnit 5のテストをコマンドラインから実行するAPIモジュールです。実行結果はコンソールに表示されます。
■ junit-commons:すべてのモジュールが使用する共通APIモジュールです。
■ junit4-runner:JUnit 5テストをJUnit 4で実行するためのAPIモジュールです。IDEやビルド・ツールはまだJUnit 5テストをサポートしていないため、このモジュールの利用によってJUnit 4ベースの実装をJUnit 5に移行しやすくなります。
■ surefire-junit5:JUnit 5テストをJUnit 4上で実行するためのMaven Surefireプラグインが組み込まれたJUnitGen5Providerを含むモジュールです。
■ junit-gradle:JUnit 5テストをJUnit 4上で実行するためのGradleビルドが組み込まれたJUnit5Pluginを含むモジュールです。JUnit 5のモジュールの主な目的の1つは、テストを実行するAPIとテストを実装するAPIを分離することです。
JUnit 5テストの構造それでは、いくつかのJUnit 5テストを見ていきます。まずは、リスト1の簡単なJUnitテストをご覧ください。
リスト1:import org.junit.gen5.api.Test;
class SimpleTest {
@Test void simpleTestIsPassing() { org.junit.gen5.api.Assertions. assertTrue(true); }}
Listing 1のような簡単なJUnit 5テスト・クラスを一目見ただけでは、JUnit 4テスト・クラスとほとんど変わらないように見えます。大きく違う点は、テスト・クラスやテスト・メソッドをpublic修飾子で定義する必要がなくなった点です。また、@Testなどのアノテーションはすべて、org.junit.gen5.apiという新しいパッケージに移動しています。このパッケージはインポートする必要があります。
アノテーションの活用JUnit 5では、アノテーション・セットが改定されています。アノテーションは、テストの実装において欠かせない機能であると筆者は考えています。アノテーションは、個々に宣言することも、組み合わせてカスタムのアノテーションを作成することも可能です。次のセクションでは各アノテーションについて、例とともに詳しく説明します。 @DisplayName:@DisplayNameアノテーションを使用して、テスト・クラスやそのメソッドの名前を表示することができるようになりました。リスト2のように、説明には空白および特殊文字を含めることができます。また、Jなどの絵文字も含めることができます。
リスト2:@DisplayName("This is my awesome test class")class SimpleNamedTest {
@DisplayName("This is my lonely test method") @Test void simpleTestIsPassing() { assertTrue(true); }}
@Disabled:@Disabledアノテーションは、JUnit 4の@Ignoreアノテーションと同様の意味を持ち、テスト・クラス全体やその一部のメソッドの実行を無効化できます。リスト3のように、テストを無効化する理由を、説明としてアノテーションに追加できます。
リスト3:class DisabledTest {
@Test
ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016
31
//testing / @Disabled("test is skipped") void skippedTest() { fail("feature not implemented yet"); }}
@Tagsと@Tag:テスト・クラスやそのメソッド、あるいはその両方にタグ付けすることが可能です。タグ付けによって、実行するテストをフィルタリングできます。このアプローチは、JUnit 4のCategoriesと同様のものです。リスト4に、タグを利用したテスト・クラスの例を示します。
リスト4:@Tag("marvelous-test")@Tags({@Tag("fantastic-test"), @Tag("awesome-test")})class TagTest {
@Test void normalTest() { }
@Test @Tag("fast-test") void fastTest() { }}
テスト・ランナーにタグ名を指定して、実行するテストや除外するテストをフィルタリングすることができます。後ほどConsoleRunnerを実行する方法を詳しく説明しますが、ConsoleRunnerを使うと、必要とするタグ名を指定する‒tパラメータや、タグ名を除外する‒Tパラメータを利用できます。@BeforeAll、@BeforeEach、@AfterEach、@AfterAll:これらのアノテーションの動作は、それぞれJUnit 4の@BeforeClass、@Before、@After、@AfterClassの動作とまったく同じです。@BeforeEachアノテーションが付加されたメソッドは各@Testメソッドの前に実行され、@AfterEachアノテーションが付加されたメソッドは各@Testメソッドの後に実行されます。@BeforeAllアノテーションまたは@AfterAllアノテーションが付加されたメソッドは、
すべての@Testメソッドが実行される前または実行された後に実行されます。この4つのアノテーションは、そのクラスの@Testメソッドに対して適用されます。また、クラス階層がある場合、クラス階層に対しても適用されます(テスト階層については、次のセクションをご覧ください)。@BeforeAllアノテーションまたは@AfterAllアノテーションが付加されたメソッドは、staticメソッドとして定義する必要があります。@Nestedテスト階層:JUnit 5では、ネスト構造を利用したテスト・クラスの階層化が可能です。このオプションを使用すると、論理的にテストをグループ化して同じ親を持たせることができます。これによって、それぞれのテストに同じ初期化メソッドを適用することが簡単になります。リスト5に例を示します。
リスト5:class NestedTest {
private Queue<String> items;
@BeforeEach void setup() { items = new LinkedList<>(); }
@Test void isEmpty() { assertTrue(items.isEmpty()); } @Nested class WhenEmpty { @Test public void removeShouldThrowException() { expectThrows( NoSuchElementException.class, items::remove); } }
@Nested class WhenWithOneElement { @Test
ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016
32
//testing /
void addingOneElementShouldIncreaseSize() { items.add("Item"); assertEquals(items.size(), 1); } }}
アサーションと前提JUnit 5のorg.junit.gen5.Assertionsクラスには、テスト・メソッドの条件を扱うassertEquals、assertTrue、assertNull、assertSameなどのstaticアサーション・メソッドと、それぞれの否定版となるメソッドが含まれています。JUnit 5では、これらのアサーション・メソッドでラムダ式を活用できるように、各メソッドをオーバーロードしてjava.util.function.Supplierのインスタンスを受け取るようにしたメソッドが提供されています。これによってアサーション・メッセージを遅延評価できるようになるため、複雑になる可能性がある計算を実際にアサーションが失敗するまで遅らせることができます。リスト6に、ラムダ式を利用したアサーションを示します。
リスト6:class AssertionsTest {
@Test void assertionShouldBeTrue() { assertEquals(2 == 2, true); }
@Test void assertionShouldBeTrueWithLambda() { assertEquals(3 == 2, true, () -> "3 not equals to 2!"); }}
org.junit.gen5.Assumptionsクラスでは、assumeTrue、assumeFalse、assumingThatの各staticメソッドが提供されています。ドキュメントに記載されているように、これらのメソッドは、テストが意味を持つ条件についての前提を記述する際に便利です。前提が満
たされていない場合、コードが破損しているということではなく、テストが有用な情報を返さないということを意味するにすぎません。デフォルトのJUnitランナーは、このように無意味なテストを無視します。このアプローチにより、一連のテスト内の他のテストを実行できます。
アサーションのグループ化アサーションのリストをグループ化することも可能です。リスト7に示すassertAll staticメソッドを使用すると、すべてのアサーションが一度に実行され、すべての失敗が一度に報告されます。
リスト7:class GroupedAssertionsTest {
@Test void groupedAssertionsAreValid() { assertAll( () -> assertTrue(true), () -> assertFalse(false) ); }}
予期せぬ例外を予期するJUnit 4では、@Testアノテーションの属性として例外を宣言することによって、その例外を処理できます。以前のバージョンでは、例外処理にtry-catchブロックを使用する必要があったため、この点は機能が強化されています。JUnit 5では、アサーション文の内部でラムダ式を使って例外を定義できるようになっています。リスト8では、アサーション内に直接例外を配置しています。
リスト8:class ExceptionsTest {
@Test void expectingArithmeticException() { assertThrows(ArithmeticException.class, () -> divideByZero()); }
ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016
33
//testing /
int divideByZero() { return 3/0; }}
また、JUnit 5では、リスト9のように例外に変数を割り当て、その値を条件にアサーションを行うこともできます。
リスト9:class Exceptions2Test {
@Test void expectingArithmeticException() { StringIndexOutOfBoundsException exception = expectThrows( StringIndexOutOfBoundsException.class, () -> "JUnit5 Rocks!".substring(-1)); assertEquals(exception.getMessage(), "String index out of range: -1"); }}
パラメータを利用したテスト・メソッドJUnit 5では、デフォルトのランナーの実装に対してパラメータを持つテストメソッドを作成できるようになりました。これによって、パラメータを動的に解決し、メソッド・レベルでインジェクションできるようになります。これを実現するのは、JUnit 5に組み込まれている2つのリゾルバであるTestInfoParameterResolverとTestReporterParameterResolverです。 メソッド・パラメータがorg.junit.gen5.api.TestInfoのインスタンスである場合、TestInfoParameterResolverがそのインスタンスをパラメータとして提供します。テスト名やその表示名は、TestInfoインスタンスから取得できます。 メソッド・パラメータがorg.junit.gen5.api.TestReporterのインスタンスである場合、TestReporterParameterResolverがそのインスタンスをパラメータとして提供します。リスト10の例に示すように、キ
ーと値のペアをレポーター・インスタンスに対して発行し、IDEやレポーティング・ツールで使用することができます。
リスト10:class ResolversTest {
@Test @DisplayName("my awesome test") void shouldAssertTrue( TestInfo info, TestReporter reporter) { System.out.println( "Test " + info.getDisplayName() + " is executed."); assertTrue(true); reporter.publishEntry( "a key", "a value"); }}
TestInfoまたはTestReporterのインスタンスは、@BeforeAll、@BeforeEach、@AfterEach、@AfterAllの各アノテーションが付加されたメソッドにもインジェクションできます。
新しい拡張モデルJUnit 5では、さまざまな拡張ポイントを組み合わせるための拡張可能なAPIが提供されています。このAPIを利用して、サード・パーティ製のフレームワークをJUnitのテスト・メカニズムに組み込むこともできます。JUnit 4では、Runnerや@Rule、@ClassRuleを使ってこれを実現できました。JUnit 5では統合されたAPIが提供されており、@ExtendWithで複数の拡張をまとめて定義できます。Listing 11は、Mockitoフレームワークを組み込む例です。
リスト11:public interface User { String getName();}
ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016
34
//testing /
@ExtendWith(MockitoExtension.class)class ExtensionsTest {
@BeforeEach void init(@InjectMock User user) { when(user.getName()).thenReturn("Mert"); }
@Test void parameterInjectionWorksOk( @InjectMock User user) { assertEquals("Mert", user.getName()); }}
MockitoExtensionクラスは、実行時にメソッド・パラメータを動的に解決する拡張ポイントであるMethodParameterResolverを実装しています。そのため、@BeforeEachアノテーションおよび@Testアノテーションが付加されたメソッドで、マーカー・アノテーション@InjectMockが付加されたパラメータはモック化され格納されます。MockitoExtensionと@InjectMockのソース・コードはオンライン
でご覧いただけます。
JUnit 5の実行現在のところ、直接JUnit 5ベースのテストの実行をサポートしているIDEやビルド・ツールはありませんが、まもなく登場することになるでしょう。JUnit 5では、JUnit 5ベースのテストを組み込んで実行する手段として、2つの方法が提供されています。その1つは、テストを実行するコマンドライン・アプリケーションであるConsoleRunnerです。もう1つは、JUnit 4を統合したIDEやビルド・ツール上でテストを実行するJUnit 4ランナーです。JUnit 4ランナーは、GradleとMavenの両方のビルド・システムと統合されています。ConsoleRunnerは、次のJavaコマンドで実行できます。ConsoleRunnerを実行するためには、必要なJARファイルをクラスパスに含めておく必要があるため、正しいバージョンのアーティファクトが揃っていることを確認してください。
java -cp /path/to/junit-console-5.0.0-ALPHA.jar: /path/to/jopt-simple-4.9.jar: /path/to/junit-commons-5.0.0-ALPHA.jar: /path/to/junit-launcher-5.0.0-ALPHA.jar: /path/to/junit-engine-api-5.0.0-ALPHA.jar org.junit.gen5.console.ConsoleRunner
[編集注:このコマンドは、1行で入力する必要があります]JUnit 4しかサポートしていないIDEでJUnit 5のテストを実行すること
も可能です。そのためには、リスト12のように@RunWithアノテーションでorg.junit.gen5.junit4.runner.JUnit5クラスを定義します。
リスト12:@RunWith(JUnit5.class)public class RunWithTest {
@Test public void simpleTestIsPassing() { org.junit.gen5.api.Assertions. assertTrue(true); }}
クラスパスでは、JUnit 4の依存性だけでなく、junit4-runnerモジュールとjunit5-engineモジュールの依存性も定義しておく必要があります。
ORACLE.COM/JAVAMAGAZINE ///////////////////////////////// MAY/JUNE 2016
35
//testing /
まとめJUnitチームは、以前のバージョンが抱えていたほぼすべての制限に対処する、新しく再設計されたバージョンのJUnitを提供しています。ただし、JUnit 5 APIはまだ変更される可能性がある点に注意してください。JUnitチームは、パブリックな型に@APIアノテーションを付加し、Experimental、Maintained、Stableなどの値を割り当てています。 JUnit 5を使ってみて、2016年後半に予定されているリリースに備え
てください。皆さんのJUnitにいつも緑色のバーが表示されますように。</article>
Mert Çalis¸kan(@mertcal):Java Champion。『PrimeFaces Cookbook』(Packt Publishing。第1版は2013年、第2版は2015年)、『Beginning Spring』(Wiley Publications、2015年)の共著者。トルコで一番活発なJavaユーザー・グループAnkaraJUGの創立者。
JUnit 5の公式ドキュメント
learn more