はじめに#
TDD(テスト駆動開発)を実践するには、テストを素早く書いて実行できる環境が不可欠です。Red-Green-Refactorサイクルを円滑に回すためには、テストの実行が数秒で完了し、結果がすぐにフィードバックされる環境を整える必要があります。
Javaにおける最も標準的なテスティングフレームワークはJUnitです。特にJUnit 5(JUnit Jupiter) は、JUnit 4から大幅に進化し、モダンなテスト記述スタイル、拡張モデル、パラメータ化テストなど、TDDを効率的に実践するための機能を豊富に備えています。
この記事では、MavenとGradleそれぞれでJUnit 5の環境を構築し、IntelliJ IDEAとVS Codeでテストを実行する手順を解説します。さらに、TDDを始めるために最低限知っておくべきアノテーションとAssertionsの基本も紹介します。
前提条件#
本記事の手順を実行するには、以下の環境が必要です。
| 項目 |
要件 |
| Java |
JDK 17以上(JUnit 5.11以降はJava 17が必須) |
| ビルドツール |
Maven 3.6以上 または Gradle 4.6以上 |
| IDE |
IntelliJ IDEA 2023.1以上 または VS Code + Extension Pack for Java |
JUnit 5はJava 17以上を必要とします。JDKのバージョンが古い場合は、事前にアップデートしてください。
JUnit 5のアーキテクチャ#
JUnit 5は、従来のJUnitと異なり、3つのサブプロジェクトで構成されています。
flowchart TB
subgraph JUnit5["JUnit 5"]
JP["JUnit Platform<br/>テスト実行基盤"]
JJ["JUnit Jupiter<br/>新しいテストAPI"]
JV["JUnit Vintage<br/>JUnit 3/4互換"]
end
JP --> JJ
JP --> JV
style JP fill:#e6f3ff,stroke:#0066cc,color:#000000
style JJ fill:#e6ffe6,stroke:#00cc00,color:#000000
style JV fill:#fff2e6,stroke:#cc6600,color:#000000
| コンポーネント |
役割 |
| JUnit Platform |
IDEやビルドツールからテストを起動するための基盤 |
| JUnit Jupiter |
JUnit 5でテストを書くためのAPI(@Test、Assertionsなど) |
| JUnit Vintage |
JUnit 3/4で書かれた既存テストを実行するためのエンジン |
TDDで新規プロジェクトを始める場合は、JUnit Jupiterのみを依存関係に追加すれば十分です。
Mavenでの環境構築#
プロジェクトの作成#
まず、Mavenプロジェクトを作成します。以下のコマンドを実行してください。
1
2
3
4
5
6
|
mvn archetype:generate \
-DgroupId=com.example \
-DartifactId=tdd-demo \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.5 \
-DinteractiveMode=false
|
pom.xmlの設定#
生成されたpom.xmlを開き、JUnit 5の依存関係とMaven Surefire Pluginを設定します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="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.example</groupId>
<artifactId>tdd-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.11.4</junit.version>
</properties>
<dependencies>
<!-- JUnit Jupiter(JUnit 5のテストAPI) -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Surefire Plugin: テスト実行に必須 -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
</plugin>
</plugins>
</build>
</project>
|
junit-jupiterアーティファクトを追加するだけで、JUnit Jupiter APIとエンジンの両方が含まれます。Maven Surefire Plugin 3.0以上は、JUnit Platformをネイティブでサポートしているため、追加設定なしでJUnit 5のテストを実行できます。
BOM(Bill of Materials)を使用する場合#
複数のJUnitアーティファクトを使用する場合は、BOMを使ってバージョンを一元管理できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.11.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
<!-- BOM使用時はバージョン指定不要 -->
</dependency>
</dependencies>
|
テストの実行#
以下のコマンドでテストを実行します。
1
2
3
4
5
6
7
8
|
# すべてのテストを実行
mvn test
# 特定のテストクラスのみ実行
mvn test -Dtest=CalculatorTest
# 特定のテストメソッドのみ実行
mvn test -Dtest=CalculatorTest#shouldAddTwoNumbers
|
Gradleでの環境構築#
プロジェクトの作成#
Gradleプロジェクトを作成します。
1
2
|
mkdir tdd-demo && cd tdd-demo
gradle init --type java-application --dsl kotlin --test-framework junit-jupiter
|
build.gradle.ktsの設定(Kotlin DSL)#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
plugins {
java
}
group = "com.example"
version = "1.0-SNAPSHOT"
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
repositories {
mavenCentral()
}
dependencies {
// JUnit Jupiter
testImplementation("org.junit.jupiter:junit-jupiter:5.11.4")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.test {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
}
|
build.gradleの設定(Groovy DSL)#
Groovy DSLを使用する場合は以下のように設定します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
plugins {
id 'java'
}
group = 'com.example'
version = '1.0-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
test {
useJUnitPlatform()
testLogging {
events 'passed', 'skipped', 'failed'
}
}
|
useJUnitPlatform()の指定がテスト実行に必須です。この設定がないと、GradleのtestタスクがJUnit 5のテストを認識しません。
テストの実行#
1
2
3
4
5
6
7
8
9
10
11
|
# すべてのテストを実行
./gradlew test
# 特定のテストクラスのみ実行
./gradlew test --tests CalculatorTest
# 特定のテストメソッドのみ実行
./gradlew test --tests "CalculatorTest.shouldAddTwoNumbers"
# テストを強制的に再実行(キャッシュを無視)
./gradlew test --rerun-tasks
|
IDEでのテスト実行#
IntelliJ IDEAの場合#
IntelliJ IDEAはJUnit 5をネイティブでサポートしています。特別なプラグインは不要です。
- プロジェクトをインポート(
pom.xmlまたはbuild.gradle.ktsを開く)
- Mavenの場合は
Reload All Maven Projects、Gradleの場合はReload All Gradle Projectsを実行
- テストクラスを開き、クラス名またはメソッド名の横にある再生ボタンをクリック
キーボードショートカットを使用すると、さらに効率的にテストを実行できます。
| 操作 |
Windows/Linux |
macOS |
| カーソル位置のテストを実行 |
Ctrl + Shift + F10 |
Ctrl + Shift + R |
| 前回実行したテストを再実行 |
Shift + F10 |
Ctrl + R |
| デバッグモードでテスト実行 |
Shift + F9 |
Ctrl + D |
VS Codeの場合#
VS CodeでJUnit 5のテストを実行するには、Extension Pack for Javaをインストールします。
- 拡張機能から「Extension Pack for Java」をインストール
- プロジェクトフォルダを開く
- テストクラスを開くと、
@Testアノテーションの上に「Run Test」「Debug Test」のコードレンズが表示される
- クリックしてテストを実行
サイドバーの「Testing」アイコンからテストエクスプローラーを開くと、プロジェクト内のすべてのテストを一覧表示し、一括実行することもできます。
ディレクトリ構造#
Javaの標準的なプロジェクト構造では、テストコードはsrc/test/javaディレクトリに配置します。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
tdd-demo/
├── pom.xml (または build.gradle.kts)
├── src/
│ ├── main/
│ │ └── java/
│ │ └── com/
│ │ └── example/
│ │ └── Calculator.java
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── CalculatorTest.java
|
テストクラスは、テスト対象クラスと同じパッケージ構造に配置するのが慣例です。これにより、パッケージプライベート(デフォルトアクセス)のメンバーにもアクセスできます。
テストクラスの命名規則#
Maven Surefire Pluginは、デフォルトで以下のパターンに一致するクラスをテストクラスとして認識します。
| パターン |
例 |
**/Test*.java |
TestCalculator.java |
**/*Test.java |
CalculatorTest.java |
**/*Tests.java |
CalculatorTests.java |
**/*TestCase.java |
CalculatorTestCase.java |
最も一般的なのは*Test.javaのパターンです。TDDを実践する場合は、このルールに従ってテストクラスを命名してください。
JUnit 5の基本アノテーション#
TDDを始めるために最低限知っておくべきアノテーションを紹介します。
@Test#
メソッドがテストメソッドであることを示します。JUnit 4と異なり、public修飾子は不要です。
1
2
3
4
5
6
7
8
9
10
11
|
import org.junit.jupiter.api.Test;
class CalculatorTest {
@Test
void shouldAddTwoNumbers() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result);
}
}
|
@DisplayName#
テスト結果に表示される名前をカスタマイズします。日本語での記述も可能で、テストの意図を明確に伝えられます。
1
2
3
4
5
6
7
8
9
10
11
|
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
class CalculatorTest {
@Test
@DisplayName("2つの正の整数を加算すると、その合計が返される")
void shouldAddTwoPositiveNumbers() {
// ...
}
}
|
@BeforeEach / @AfterEach#
各テストメソッドの実行前後に呼び出されるセットアップ/クリーンアップメソッドを定義します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator();
}
@AfterEach
void tearDown() {
// リソースのクリーンアップ(必要な場合)
}
@Test
void shouldAddTwoNumbers() {
assertEquals(5, calculator.add(2, 3));
}
@Test
void shouldSubtractTwoNumbers() {
assertEquals(1, calculator.subtract(3, 2));
}
}
|
@BeforeAll / @AfterAll#
テストクラス内のすべてのテストの実行前後に一度だけ呼び出されます。staticメソッドである必要があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
class DatabaseTest {
@BeforeAll
static void initDatabase() {
// データベース接続の初期化
}
@AfterAll
static void closeDatabase() {
// データベース接続のクローズ
}
}
|
@Disabled#
テストを一時的に無効化します。スキップされたテストはレポートに表示されます。
1
2
3
4
5
6
7
8
9
10
11
|
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class CalculatorTest {
@Test
@Disabled("Issue #123 が解決するまで無効化")
void shouldDivideByZero() {
// 未実装のテスト
}
}
|
@Nested#
テストをグループ化し、階層構造を作成します。関連するテストをまとめることで、可読性が向上します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
@DisplayName("Calculatorクラスのテスト")
class CalculatorTest {
@Nested
@DisplayName("addメソッド")
class AddMethod {
@Test
@DisplayName("正の整数同士を加算できる")
void shouldAddPositiveNumbers() {
// ...
}
@Test
@DisplayName("負の整数同士を加算できる")
void shouldAddNegativeNumbers() {
// ...
}
}
@Nested
@DisplayName("subtractメソッド")
class SubtractMethod {
// ...
}
}
|
Assertionsの基本#
JUnit 5のアサーションはorg.junit.jupiter.api.Assertionsクラスで提供されます。staticインポートを使用すると、コードがすっきりします。
1
|
import static org.junit.jupiter.api.Assertions.*;
|
基本的なアサーション#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@Test
void basicAssertions() {
// 等価性の検証
assertEquals(4, calculator.add(2, 2));
assertNotEquals(5, calculator.add(2, 2));
// 真偽値の検証
assertTrue(calculator.isPositive(5));
assertFalse(calculator.isPositive(-5));
// null検証
assertNotNull(calculator);
assertNull(calculator.getLastError());
// 同一性の検証(同じインスタンスか)
assertSame(expected, actual);
assertNotSame(expected, actual);
}
|
失敗メッセージの指定#
アサーションが失敗した際に表示されるメッセージを指定できます。メッセージは最後の引数として渡します。
1
2
3
4
5
6
7
8
|
@Test
void assertionWithMessage() {
int result = calculator.add(2, 3);
assertEquals(5, result, "2 + 3 は 5 になるべき");
// ラムダ式で遅延評価(失敗時のみ評価される)
assertEquals(5, result, () -> "計算結果が期待値と一致しません: " + result);
}
|
例外の検証#
1
2
3
4
5
6
7
8
9
|
@Test
void shouldThrowExceptionWhenDivideByZero() {
ArithmeticException exception = assertThrows(
ArithmeticException.class,
() -> calculator.divide(10, 0)
);
assertEquals("/ by zero", exception.getMessage());
}
|
グループアサーション#
複数のアサーションをグループ化し、すべての失敗を一度にレポートします。
1
2
3
4
5
6
7
8
9
10
|
@Test
void groupedAssertions() {
Person person = new Person("John", "Doe");
assertAll("person",
() -> assertEquals("John", person.getFirstName()),
() -> assertEquals("Doe", person.getLastName()),
() -> assertNotNull(person.getEmail())
);
}
|
タイムアウトの検証#
1
2
3
4
5
6
7
|
@Test
void shouldCompleteWithinTimeout() {
assertTimeout(Duration.ofSeconds(2), () -> {
// 2秒以内に完了すべき処理
service.processData();
});
}
|
最初のテストを書いてみる#
環境構築が完了したら、TDDの実践としてシンプルな計算機クラスを作成してみましょう。
Redフェーズ: 失敗するテストを書く#
まず、src/test/java/com/example/CalculatorTest.javaを作成します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package com.example;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator();
}
@Test
@DisplayName("2つの整数を加算すると合計が返される")
void shouldAddTwoNumbers() {
int result = calculator.add(2, 3);
assertEquals(5, result);
}
}
|
この時点でCalculatorクラスは存在しないため、コンパイルエラーになります。これがRedフェーズです。
Greenフェーズ: テストを通す最小限のコードを書く#
src/main/java/com/example/Calculator.javaを作成します。
1
2
3
4
5
6
7
8
|
package com.example;
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
|
テストを実行すると、緑色で成功が表示されます。
1
2
3
|
mvn test
# または
./gradlew test
|
Refactorフェーズ: コードを改善する#
現時点ではコードがシンプルなので、大きなリファクタリングは不要です。ただし、TDDではこのタイミングでコードの改善を検討します。
このサイクルを繰り返しながら、減算、乗算、除算といった機能を追加していきます。
トラブルシューティング#
テストが認識されない#
以下の点を確認してください。
- テストクラス名が命名規則に従っているか(
*Test.javaなど)
@Testアノテーションがインポートされているか(JUnit 4ではなくJUnit 5のorg.junit.jupiter.api.Test)
- Maven Surefire Plugin または Gradle の
useJUnitPlatform()が設定されているか
「No tests found」エラー#
Maven Surefire Plugin のバージョンが古い可能性があります。3.0.0以上にアップグレードしてください。
1
2
3
4
|
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
</plugin>
|
IDE でテストが実行できない#
プロジェクトの再インポートを試してください。
- IntelliJ IDEA:
File > Invalidate Caches / Restart
- VS Code:
Java: Clean Java Language Server Workspace
まとめ#
この記事では、JUnit 5を使ったTDD環境の構築方法を解説しました。
ポイントを振り返ります。
- JUnit 5は JUnit Platform、JUnit Jupiter、JUnit Vintage の3つで構成される
- Maven では
junit-jupiter 依存関係と Maven Surefire Plugin 3.0以上を設定
- Gradle では
useJUnitPlatform() の指定が必須
@Test、@BeforeEach、@DisplayName が基本アノテーション
assertEquals、assertTrue、assertThrows が基本的なアサーション
環境が整ったら、次は実際にTDDのRed-Green-Refactorサイクルを回しながらコードを書いてみてください。テストを先に書くという習慣が身につくと、自然と設計品質が向上し、安心してリファクタリングできるようになります。
参考リンク#