こんにちは、しんどーです。
気づいたら入社8ヶ月くらい経ってました。
さて、待望のJUnit 5のGA版が今年9月にリリースされました!
この記事ではJUnit 5の概要と新機能の一部をご紹介したいと思います。
全部User Guideに書いてあるとか言わない
JUnitとは、言わずと知れたJavaのテスティングフレームワークであり、
デファクトスタンダードの地位にあります。ですが現行のJUnit 4系の最初の
メジャーバージョンリリースはすでに10年ほど前であり、保守性の低下が問題に
なっていました。
そこでJUnitの刷新を目指すべく、JUnit 5プロジェクトが立ち上げられました。
Junit 5の特徴を簡単に述べると、
といったことが挙げられます。
JUnit 5のAPIは、JUnit 4のAPIとは互換性がありません。
したがって全く新しいフレームワークとして理解したほうがいいかもしれません。
で、JUnit 5って使えるの?
結論から言うと、一から再設計を行ったことによって、
JUnit 4のイマイチな部分が軒並み改善された印象です。
これまで設計上の制約からか、謎のお決まりごと(@Enclosedなクラスはstaticでないといけない等)が
多かったですが、JUnit 5はより自然で直感的なインターフェースデザインになっています。
お決まりコードも減りますので、多少のテストコード量の削減も期待できます。
ただし、すでに述べたようにJUnit 4とは互換性がありませんので、
既存のテストを書き換えるのはおすすめできません。
また新規のプロジェクトで使う場合も、IDE・ビルドツール・フレームワーク等の
サポートにはご注意ください。
例えば、後述しますがJUnit 5.0系とmaven-surefire-pluginの2.20系の連携はバグがあり動作しません。
Springユーザであれば、Spring 5からJUnit 5のサポートがあるので、一緒に使い始めるのがいいかもしれません。
(逆にSpring 4でJUnit 5を使うのは大変かと思います。。)
JUnit 5は3つのサブプロジェクトから構成されています。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnitテストを実行するための基盤を提供します。Maven用、Gradle用のプラグイン等が用意されていますので、
各プロジェクトの環境に合わせたモジュールを選択します。
テストを記述するAPI(@Test
etc...)とテストエンジンを提供します。
基本的に、「JUnit 5」と言った場合はJUnit JupiterのAPIを指すことが多いと思います。
JUnit Platform上でJUnit 3 or 4を動かすためのテストエンジン等を提供します。
この記事では特に触れません。
Getting Started
さて、早速JUnit 5を動かしてみましょう。JUnit 4はたった一つのjarにまとまっていましたが、JUnit 5は複数の
モジュールの組み合わせで動作します。(おかげで最初の1時間を無駄にしました。。)
シンプルなMavenプロジェクトを作成して、JUnit 5を動かします。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlnsxsi="http://www.w3.org/2001/XMLSchema-instance"
xsischemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.rshindo</groupId>
<artifactId>junit5-sample</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<projectbuildsourceEncoding>UTF-8</projectbuildsourceEncoding>
<javaversion>1.8</javaversion>
<junitjupiterversion>5.0.2</junitjupiterversion>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.0.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
実はJUnit 5.0系 は現在 maven-surefire-plugin の最新版(2.20.x)に対応していません。(5.1でバグフィックスが入るようです)
https://github.com/junit-team/junit5/issues/809
ここでは 2.19.1 を利用しています。
pom.xmlを作成したら、次にテストクラスです。
import org.junit.jupiter.api.Test;
public class AppTest {
@Test
void testApp() {
assertTrue(true);
}
}
テストクラスを書いたら、最後にmvn test
でテストを実行しましょう!
基本のテスト
JUnit 5のAPIはJUnit 4とは互換性がありませんが、 基本的なアノテーションは
パッケージと名前を変えるだけでほぼ同じように動きます。
JUnit4 |
JUnit5 |
@Test |
@Test |
@Before |
@BeforeEach |
@BeforeClass |
@BeforeAll |
@After |
@AfterEach |
@AfterClass |
@AfterAll |
@Ignore |
@Disabled |
前処理・後処理
前処理・後処理は @BeforeEach
@BeforeAll
@AfterEach
@AfterAll
を使います。
@BeforeAll
static void setUpAll() {
System.out.println("before all tests");
}
@BeforeEach
void setUp() {
System.out.println("before each test");
}
@Test
void test1() {
System.out.println("test 1");
}
@Test
void test2() {
System.out.println("test 2");
}
@AfterEach
void tearDown() {
System.out.println("after each test");
}
@AfterAll
static void tearDownAll() {
System.out.println("after all tests");
}
このときの出力は次のようになります。
before all tests
before each test
test 1
after each test
before each test
test 2
after each test
after all tests
テストケース名
テストの名前は @DisplayName
で指定可能です。これまではテストメソッド名を日本語で書く人も多かったと思いますが、 @DisplayName
であれば文字種の縛りもなく自由にテスト名を変えられます。
@Test
@DisplayName("1 + 1 = 2になるテスト")
void plusTest() {
assertEquals(1 + 1, 2);
}
@Test
@DisplayName("😁")
void emojiTest() {
}
Eclipseで実行すると以下のように表示されます。
注)mavenでレポート出力したら@DisplayNameが効いていませんでした。。
ラムダ式に対応した、便利なアサートAPIが追加されています。
もちろん、assertTrue
や assertEquals
もあります。
あるオブジェクトのプロパティを全て検証したいとき、これまでは以下のように書いていたと思います。
@Test
public void employeeTest() {
Employee employee = employeeService.findById(1);
assertEquals("Aoi", employee.getFirstName());
assertEquals("Miyamori", employee.getLastName());
}
仮に1つ目のアサーションが失敗したとします。
その時点で例外が投げられ、その次のアサーションは実行されません。
1つ目のアサーションが通るように修正したら、
今度は2つ目のアサーションも失敗していることに気づいてまた修正に戻って・・・という経験をした方も多いと思います。
そんなときに役に立つのが、アサーションのグループ化です。
@Test
void employeeTest() {
Employee employee = employeeService.findById(1);
assertAll("employee",
() -> assertEquals("Aoi", employee.getFirstName()),
() -> assertEquals("Miyamori", employee.getLastName())
);
}
assertAll
の中にラムダ式で複数のアサーションを渡しています。
このように記述することで、片方のアサーションが失敗しても、
もう片方のアサーションを実行することができるのです!
JUnit 4での例外のアサーションはこうでした。
@Test(exception = NumberFormatException.class)
public void exceptionTest() {
Long.valueOf(null);
fail("Exception not thrown");
}
これが assertThrows
によってこう変わります。
@Test
void succeedingTest() {
NumberFormatException ex =
assertThrows(NumberFormatException.class,
() -> Long.valueOf(null));
assertEquals(ex.getMessage(), "null");
}
assertThrows
は、例外がthrowされなかった場合には
アサーションが失敗となります。@Test
には何も書きません。
assertThrows
は例外オブジェクトを返しますので、
そこからメッセージの検証などもできます。
また、JUnit 4で例外が発生しなかったケースのために書いていた
fail("Exception not thrown")
という呪文も必要なくなるのです!
assertThat
様々なアサーションAPIが追加された一方、 assertThat
はJUnit 5では提供されていません。
JUnit 5の方針として、アサーションライブラリは開発者が好きなもの使ってね、というスタンスのようです。
assertThat
を使いたい場合は、Hamcrestなどのサードパーティのライブラリを導入すればOKです!
構造化テスト
JUnit 4の @Enclosed
の代わりに @Nested
が導入されました。
public class EmployeeServiceTest {
EmployeeService service;
@BeforeEach
void setUp() {
this.service = new EmployeeService();
}
@Test
@DisplayName("IDで検索する")
void testFindById() {
Employee employee = service.findById(1);
assertAll("employee",
() -> assertEquals("Midori", employee.getFirstName()),
() -> assertEquals("Imai", employee.getLastName())
);
}
@Nested
@DisplayName("名前で検索する")
class FindByFirstNameStartingWithTest {
@Test
@DisplayName("firstNameが「E」から始まる")
void startsWithE() {
List<Employee> employees = service.findByFirstNameStartingWith("E");
assertEquals(employees.size(), 1);
Employee employee = employees.get(0);
assertAll("employee",
() -> assertEquals("Ema", employee.getFirstName()),
() -> assertEquals("Yasuhara", employee.getLastName())
);
}
}
}
その違いは何と言っても、インナークラスが非staticになったことです!
これによって、アウタークラスのプロパティを参照したり、前処理・後処理を共通化することができます。
まとめ
ここまでJUnit 5の新機能の一部を紹介してきました。
この他にも、パラメータ化テスト、タグ、Extensionなど
注目すべき機能がたくさんあります。
この機会にJUnit 5を是非触ってみてください!
(できれば続き書きたいなあ。。)
Acroquest Technologyでは、キャリア採用を行っています。
- ビッグデータ(Hadoop/Spark、NoSQL)、データ分析(Elasticsearch、Python関連)、Web開発(SpringCloud/SpringBoot、AngularJS)といった最新のOSSを利用する開発プロジェクトに関わりたい。
- マイクロサービス、DevOpsなどの技術を使ったり、データ分析、機械学習などのスキルを活かしたい。
- 社会貢献性の高いプロジェクトや、顧客の価値を創造するようなプロジェクトで、提案からリリースまで携わりたい。
- 書籍・雑誌等の執筆や、対外的な勉強会の開催・参加を通した技術の発信、社内勉強会での技術情報共有により、エンジニアとして成長したい。
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
世の中に誇れるサービスを作りたいエンジニアwanted! - Acroquest Technology株式会社のエンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com