第12回 でマルチスレッドを学びました。今回は実務で必須の 「単体テスト(JUnit)」 を学びます。
「プログラムを書いて、自分で実行して、目で確認して…」これでは人間が見落とす可能性があります。 JUnit(ジェイユニット) は、自動でテストしてくれる仕組み。書いたコードが正しいかを マシンが代わりにチェックしてくれる、現代の Java 開発では欠かせない道具です。
この記事のゴール
「JUnit でテストを書く・実行する」「@Test と assertEquals の使い方」「テスト前後の準備・後片付け(@BeforeEach / @AfterEach)」「よいテストの書き方」が理解できることです。
なぜテストを書くのか?
JUnit を学ぶ前に、テストの意義をひと言で理解しましょう。
💡 テストとは: 「このコードはちゃんと動くはず」を コードで証明する こと。
テストがあると何が嬉しい?
- バグの早期発見: 修正したつもりが別の場所を壊しても、すぐ気づける
- 安心してリファクタリング: 既存テストが通れば動作は保たれている
- 仕様書としても機能: 「このメソッドはこういう挙動」がテストコードで明確化
- ドキュメント不要: 入出力例がコードに残るので、新人が読むだけで理解できる
準備: プロジェクトに JUnit を入れる
JUnit は標準ライブラリではなく 外部ライブラリなので、プロジェクトに追加が必要です。
Maven の場合(pom.xml)
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
Gradle の場合(build.gradle)
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
test {
useJUnitPlatform()
}
💡 IntelliJ IDEA なら、新規プロジェクト作成時に 「Add Sample Code」+「JUnit 5」 を選べば自動セットアップ完了。本記事では JUnit 5(Jupiter) を前提に解説します。
テスト対象のクラスを用意
まず、テストする対象の Calculator クラス を作ります。
// src/main/java/Calculator.java
public class Calculator {
// 足し算するメソッド
public int add(int a, int b) {
return a + b;
}
// 引き算するメソッド
public int subtract(int a, int b) {
return a - b;
}
// 割り算するメソッド(0 除算で例外)
public int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("0 で割ることはできません");
}
return a / b;
}
}
はじめての JUnit テスト
// src/test/java/CalculatorTest.java
import org.junit.jupiter.api.Test; // @Test を使うため
import static org.junit.jupiter.api.Assertions.*; // assertEquals 等を使うため
public class CalculatorTest {
// @Test を付けたメソッドが「1 つのテストケース」になる
@Test
public void add_2_plus_3_equals_5() {
// ── ① 準備(Arrange):テスト対象を用意 ──
Calculator calc = new Calculator();
// ── ② 実行(Act):対象メソッドを呼ぶ ──
int result = calc.add(2, 3);
// ── ③ 検証(Assert):期待通りかチェック ──
assertEquals(5, result); // 「期待値=5, 実際の値=result」
}
}
コード解説
| 部分 | 意味 |
|---|---|
@Test | このメソッドはテストケース、と JUnit に伝える目印(アノテーション) |
import static ...Assertions.* | assertEquals などを Assertions.assertEquals と書かずに済ませる |
assertEquals(期待値, 実際の値) | 2 つが等しければテスト成功、違えば失敗 |
実行方法
- IntelliJ: テストメソッド左の ▶ ボタンをクリック
- コマンドライン:
mvn test(Maven)、gradle test(Gradle)
緑のチェックマーク ✓ が出れば成功、赤の × が出れば失敗です。
AAA パターン(Arrange-Act-Assert)
良いテストの王道は AAA パターン:
- Arrange(準備): テスト対象とデータを用意
- Act(実行): テスト対象を実行
- Assert(検証): 期待通りか確認
このパターンを意識して書くだけで、読みやすいテストになります。
よく使う assert メソッド
| メソッド | 説明 | 例 |
|---|---|---|
assertEquals(期待, 実際) | 2 つが等しいか | assertEquals(5, result) |
assertNotEquals(期待しない, 実際) | 等しくないか | assertNotEquals(0, result) |
assertTrue(条件) | 条件が true か | assertTrue(name.startsWith("佐")) |
assertFalse(条件) | 条件が false か | assertFalse(list.isEmpty()) |
assertNull(値) | null か | assertNull(user.getEmail()) |
assertNotNull(値) | null でないか | assertNotNull(result) |
assertThrows(クラス, 実行) | 例外が投げられるか | (下記参照) |
例外を確認: assertThrows
@Test
public void divide_by_zero_throws_exception() {
Calculator calc = new Calculator();
// 「ラムダ式の中身を実行したら、IllegalArgumentException が出るはず」
assertThrows(IllegalArgumentException.class, () -> {
calc.divide(10, 0); // ここで例外が出るはず
});
}
ポイント: () -> { ... } で実行する処理を渡し、その中で 指定した例外が起きるかを検証。例外が起きないと テスト失敗になります。
複数テストケースをまとめる
1 つのテストクラスに 複数の @Test メソッド を書けます。
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class CalculatorTest {
@Test
public void add_positive_numbers() {
Calculator calc = new Calculator();
assertEquals(5, calc.add(2, 3));
}
@Test
public void add_negative_numbers() {
Calculator calc = new Calculator();
assertEquals(-1, calc.add(-3, 2));
}
@Test
public void subtract_works() {
Calculator calc = new Calculator();
assertEquals(10, calc.subtract(15, 5));
}
@Test
public void divide_works() {
Calculator calc = new Calculator();
assertEquals(3, calc.divide(10, 3)); // 10/3 は整数除算で 3
}
@Test
public void divide_by_zero_throws() {
Calculator calc = new Calculator();
assertThrows(IllegalArgumentException.class, () -> calc.divide(10, 0));
}
}
@BeforeEach で重複を減らす
「毎回 new Calculator() を書くのが面倒…」── そこで @BeforeEach を使います。
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class CalculatorTest {
private Calculator calc; // フィールドにしておく
// 各 @Test メソッドが実行される「前に」毎回呼ばれる
@BeforeEach
public void setUp() {
calc = new Calculator(); // 各テストごとにフレッシュなインスタンスを用意
}
@Test
public void add() {
assertEquals(5, calc.add(2, 3)); // 「calc =」を書かなくて済む!
}
@Test
public void subtract() {
assertEquals(10, calc.subtract(15, 5));
}
}
| アノテーション | 実行タイミング |
|---|---|
@BeforeEach | 各テストの前に毎回実行(初期化用) |
@AfterEach | 各テストの後に毎回実行(後片付け) |
@BeforeAll | 全テスト開始前に 1 回だけ実行(static メソッド) |
@AfterAll | 全テスト終了後に 1 回だけ実行(static メソッド) |
@DisplayName で読みやすく
テストメソッド名は長くなりがち。@DisplayName で 日本語の説明を付けられます。
@Test
@DisplayName("正の数同士の足し算が正しく動く")
public void add_positive() {
assertEquals(5, calc.add(2, 3));
}
@Test
@DisplayName("0 で割ると IllegalArgumentException が投げられる")
public void divide_by_zero() {
assertThrows(IllegalArgumentException.class, () -> calc.divide(10, 0));
}
テスト結果一覧に 日本語で結果が並ぶので、レポートとしても読みやすくなります。
よいテストを書く 5 つのコツ
編集部の鉄則
テストは 「壊れたら直すべきコードが分かる」 ためのもの。最初は「正しく動く」と「例外が出る」の 2 ケースだけでも価値があります。完璧を目指さず、まずは 書く習慣を!
① 1 テスト 1 検証(1 アサーション原則)
// ❌ 何のテストか分からない
@Test
public void test() {
assertEquals(5, calc.add(2, 3));
assertEquals(10, calc.subtract(15, 5));
assertEquals(3, calc.divide(10, 3));
}
// ⭕ それぞれ分けて、何のテストか名前で分かるように
@Test public void add_returns_sum() { ... }
@Test public void subtract_returns_difference() { ... }
@Test public void divide_returns_quotient() { ... }
② 「正常系」と「異常系」を両方書く
- 正常系: 期待通りの入力 → 期待通りの結果
- 異常系: 不正な入力 → 例外 or エラー値
③ テスト名で「何を検証しているか」を語る
@Test
public void divide_by_zero_throws_IllegalArgumentException() { ... }
// ↑ メソッド名を読むだけで内容が分かる
④ テスト同士は独立に
@BeforeEach で初期化しておけば、他のテストの影響を受けません。テスト順序に依存させない!
⑤ 失敗時のメッセージを書くと親切
assertEquals(5, calc.add(2, 3), "正の数の足し算が誤っている");
// ↑ 失敗時に「正の数の足し算が誤っている」と表示される
練習問題
以下の StringUtil クラスのテストを書いてみましょう。
テスト対象
public class StringUtil {
// 文字列を反転する(例: "hello" → "olleh")
public String reverse(String s) {
if (s == null) {
throw new IllegalArgumentException("null は渡せません");
}
return new StringBuilder(s).reverse().toString();
}
// 文字列が回文(reverse しても同じ)かチェック
public boolean isPalindrome(String s) {
if (s == null) return false;
return s.equals(reverse(s));
}
}
解答例
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.*;
public class StringUtilTest {
private StringUtil util;
@BeforeEach
public void setUp() {
util = new StringUtil();
}
// ── reverse のテスト ──
@Test
@DisplayName("通常文字列の反転が正しく動く")
public void reverse_normal_string() {
assertEquals("olleh", util.reverse("hello"));
}
@Test
@DisplayName("空文字を反転しても空文字を返す")
public void reverse_empty_string() {
assertEquals("", util.reverse(""));
}
@Test
@DisplayName("null を渡すと IllegalArgumentException が出る")
public void reverse_null_throws_exception() {
assertThrows(IllegalArgumentException.class, () -> util.reverse(null));
}
// ── isPalindrome のテスト ──
@Test
@DisplayName("回文を渡すと true を返す")
public void isPalindrome_true() {
assertTrue(util.isPalindrome("level")); // level → reverse でも level
}
@Test
@DisplayName("回文でない文字列で false を返す")
public void isPalindrome_false() {
assertFalse(util.isPalindrome("hello"));
}
@Test
@DisplayName("null を渡すと false を返す")
public void isPalindrome_null_returns_false() {
assertFalse(util.isPalindrome(null));
}
}
まとめ
第 13 回では、以下を学びました。
- ✅ JUnit は Java の単体テストフレームワーク
- ✅
@Testでテストケースを宣言 - ✅ AAA パターン(Arrange-Act-Assert)で書く
- ✅
assertEquals/assertTrue/assertThrowsなどの検証メソッド - ✅
@BeforeEachで重複初期化を共通化 - ✅
@DisplayNameで読みやすい日本語タイトル - ✅ 正常系と異常系の両方をカバーするのが基本
次回はいよいよ 最終回! 「Java フレームワーク(Spring Boot 入門)」 を学びます。 現代の Java 開発の主役 である Spring Boot で、Web API を作る基本を学びましょう。お楽しみに!