JUnitは、Javaでプログラムの部品(メソッドやクラス)が正しく動くかを自動で確かめる「単体テスト」を書くための、定番のフレームワークです。@Test を付けたメソッドの中で、処理を実行し、結果が期待どおりかを assertEquals などで確認します。

現在の主流はJUnit 5(Jupiter)で、2025年にはJUnit 6も公開されました。JUnit 6はJava 17以上が必要になるなどの変更がありますが、テストの書き方の基本はJUnit 5と共通です。この記事では、環境の設定から、よく使う機能、Mockitoを使った依存の差し替え、そして何をテストすべきかという観点までを解説します。

環境の設定

Maven

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.junit</groupId>
      <artifactId>junit-bom</artifactId>
      <version>5.11.4</version>   <!-- JUnit 6 を使う場合は 6.x(Java 17以上) -->
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

テストは mvn test で実行します。Maven Surefire Pluginは、比較的新しいバージョン(3.x)を使います。

Gradle

dependencies {
    testImplementation platform("org.junit:junit-bom:5.11.4")
    testImplementation "org.junit.jupiter:junit-jupiter"
    testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}

test {
    useJUnitPlatform()   // これがないとテストが実行されない
}

テストは gradle test(または ./gradlew test)で実行します。

Spring Bootのプロジェクトでは、spring-boot-starter-test にJUnit・Mockito・AssertJが含まれているため、追加の設定は不要です。

ファイルの置き場所

src/main/java/com/example/PriceCalculator.java        ← テストされるクラス
src/test/java/com/example/PriceCalculatorTest.java    ← テストクラス(同じパッケージ)

最初のテスト

テストされるクラスです。

public class PriceCalculator {
    /** 税抜価格から税込価格を計算する(1円未満切り捨て) */
    public int withTax(int price, int ratePercent) {
        if (price < 0) {
            throw new IllegalArgumentException("価格は0以上");
        }
        return price * (100 + ratePercent) / 100;
    }
}

テストクラスです。

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

class PriceCalculatorTest {

    @Test
    void 税率10パーセントで税込価格を計算できる() {
        // 準備(Arrange)
        PriceCalculator calc = new PriceCalculator();

        // 実行(Act)
        int result = calc.withTax(1000, 10);

        // 確認(Assert)
        assertEquals(1100, result);
    }
}
  • テストメソッドには @Test を付ける
  • JUnit 5以降、テストクラスやメソッドは public でなくてよい
  • assertEquals(期待する値, 実際の値) の順番に注意する(逆にすると、失敗時のメッセージが紛らわしくなる)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「JUnitの使い方|単体テストの書き方と観点【JUnit 5・6】」

よく使うアサーション

メソッド 確認すること
assertEquals(expected, actual) 等しい
assertNotEquals(unexpected, actual) 等しくない
assertTrue(condition)・assertFalse(condition) 真・偽である
assertNull(obj)・assertNotNull(obj) nullである・nullでない
assertThrows(例外クラス, 処理) 指定した例外が発生する
assertDoesNotThrow(処理) 例外が発生しない
assertAll(...) 複数の確認をまとめて行い、すべての失敗を報告する
assertTimeout(時間, 処理) 指定時間内に終わる

例外のテスト

@Test
void 価格がマイナスなら例外になる() {
    PriceCalculator calc = new PriceCalculator();

    IllegalArgumentException e = assertThrows(
        IllegalArgumentException.class,
        () -> calc.withTax(-1, 10)
    );
    assertEquals("価格は0以上", e.getMessage());
}

assertThrows は発生した例外を返すため、メッセージの内容まで確認できます。

複数の確認をまとめる

assertAll(
    () -> assertEquals("山田", user.getName()),
    () -> assertEquals(30, user.getAge()),
    () -> assertTrue(user.isActive())
);

通常の assertEquals は、最初の失敗でテストが止まります。assertAll なら、すべての確認を実行してから、失敗したものをまとめて報告します。

テストの準備と後片付け

アノテーション 実行されるタイミング
@BeforeEach 各テストメソッドの前に毎回
@AfterEach 各テストメソッドの後に毎回
@BeforeAll テストクラスの最初に1回(staticメソッド)
@AfterAll テストクラスの最後に1回(staticメソッド)
class CartTest {
    private Cart cart;

    @BeforeEach
    void setUp() {
        cart = new Cart();   // テストごとに新しいカートを用意する
    }

    @Test
    void 商品を追加すると件数が増える() {
        cart.add(new Item("ノート", 200));
        assertEquals(1, cart.size());
    }
}

各テストは、他のテストに影響されずに単独で動くようにします。@BeforeEach で毎回新しい状態を作るのが基本です。

テストの名前と@DisplayName

@Test
@DisplayName("税率10%で、1000円の税込価格は1100円")
void withTax_tenPercent() { ... }

テストの名前は、「何を確認しているか」がわかることが大切です。テストが失敗したとき、名前を見ただけで何が壊れたかがわかるようにします。日本語のメソッド名を使うチームもあります。@DisplayName を使うと、テスト結果に表示される名前を自由に付けられます。

パラメータ化テスト:同じテストを複数の値で

境界値のように、同じ確認を複数の値で行いたいときに便利です。

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

@ParameterizedTest
@CsvSource({
    "0,    10, 0",
    "1,    10, 1",      // 1.1円 → 切り捨てで1円
    "9,    10, 9",      // 9.9円 → 9円
    "10,   10, 11",
    "1000, 8,  1080",
})
void 税込価格の計算(int price, int rate, int expected) {
    assertEquals(expected, new PriceCalculator().withTax(price, rate));
}
アノテーション 値の渡し方
@ValueSource(ints = {1, 2, 3}) 1つの引数に、値を並べて渡す
@CsvSource({...}) 複数の引数を、CSV形式で渡す
@NullAndEmptySource null と空文字を渡す
@EnumSource(Status.class) enumのすべての値を渡す
@MethodSource("メソッド名") メソッドが返す値を渡す(複雑なデータ)

Mockitoで依存するクラスを差し替える

テストしたいクラスが、データベースや外部のAPIを使っている場合、その部分を偽物(モック)に差し替えて、テストしたいクラスの処理だけを確認します。

import static org.mockito.Mockito.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    StockRepository stockRepository;     // 偽物に差し替える

    @InjectMocks
    OrderService orderService;           // テスト対象(偽物が注入される)

    @Test
    void 在庫が足りなければ注文できない() {
        when(stockRepository.findQuantity("P01")).thenReturn(0);   // 在庫0を返すようにする

        assertThrows(OutOfStockException.class,
            () -> orderService.order("P01", 1));

        verify(stockRepository, never()).decrease(any(), anyInt()); // 在庫は減らされていない
    }
}
書き方 意味
when(...).thenReturn(値) 呼ばれたら、この値を返す
when(...).thenThrow(例外) 呼ばれたら、例外を投げる
verify(mock).メソッド(...) そのメソッドが呼ばれたか確認する
verify(mock, never()).メソッド(...) 呼ばれていないことを確認する

モックを使いすぎると、テストが実装の細部に依存し、リファクタリングのたびに壊れるようになります。モックにするのは、データベース・外部API・現在日時など、テストの中で制御しにくいものに絞るのがおすすめです。

AssertJで読みやすく書く

AssertJを使うと、確認の内容を英語の文章のように書けます。

import static org.assertj.core.api.Assertions.*;

assertThat(result).isEqualTo(1100);
assertThat(names).hasSize(3).contains("山田").doesNotContain("田中");
assertThat(user.getEmail()).startsWith("yamada").endsWith("@example.com");
assertThatThrownBy(() -> calc.withTax(-1, 10))
    .isInstanceOf(IllegalArgumentException.class)
    .hasMessage("価格は0以上");

失敗したときのメッセージもわかりやすく、リストや文字列の確認を短く書けるため、実務でよく使われています。

何をテストすべきか:テストの観点

テストの書き方を覚えた後に悩むのが、「どんなテストを書けばよいか」です。次の観点で、テストの値を選びます。

観点 内容 例(税込価格の計算)
正常系 代表的な値で、期待どおり動くか 1000円、税率10%
境界値 条件が切り替わる値と、その前後 0円、1円、上限の価格
異常系 不正な値で、正しくエラーになるか マイナスの価格、null
端数 切り捨て・四捨五入の境目 1.1円、9.9円になる価格
空・0件 空のリスト、空文字、0件 商品が0件のカート
状態の組み合わせ 状態によって結果が変わる場合 会員・非会員、セール中・通常

特に効果が大きいのは、境界値と異常系です。不具合は、条件が切り替わる境目や、想定していない入力で起きやすいためです。

よいテストの特徴

特徴 内容
速い 数秒で全体を実行できる。遅いと実行されなくなる
独立している 実行する順番や、他のテストの結果に影響されない
何度実行しても同じ結果 現在日時や乱数、外部の環境に左右されない
1つのことを確認する 失敗したとき、原因がすぐにわかる
読みやすい 準備・実行・確認の3段階がはっきりしている

現在日時を使う処理は、LocalDate.now() を直接呼ばずに、Clock を外から渡せるようにしておくと、テストで日時を固定できます。

AIでテストを書くときの注意

AIのコーディング支援は、テストコードの作成が得意です。一方で、実装のコードを見て、その動きに合わせたテストを書くため、実装が間違っていても、その間違いを「正しい」と確認するテストになることがあります。

  • テストの期待値は、実装ではなく仕様から導かれているかを確認する
  • 境界値・異常系のテストが含まれているかを確認する
  • AIへの依頼では、仕様(「1円未満は切り捨て」「マイナスはエラー」など)を先に伝える

よくある質問

Q. JUnit 4とJUnit 5の違いは何ですか? A. パッケージ名(org.junit → org.junit.jupiter.api)、@Before → @BeforeEach、例外のテストの書き方(@Test(expected=...) → assertThrows)などが変わりました。JUnit 5には、JUnit 4のテストを実行する仕組み(Vintage)もあり、段階的に移行できます。

Q. privateメソッドはテストすべきですか? A. 基本的には、publicメソッドを通してテストします。privateメソッドを直接テストしたくなる場合は、その処理を別のクラスに切り出すことを検討します。

Q. テストのカバレッジ(網羅率)は何%を目指すべきですか? A. 数値そのものより、重要な処理と境界値・異常系がテストされているかが大切です。カバレッジはJaCoCoなどのツールで測定でき、テストされていない箇所を見つける手がかりとして使います。

Q. データベースを使う処理もJUnitでテストできますか? A. できます。モックに差し替える方法のほか、Testcontainersで本物のデータベースをコンテナで起動してテストする方法もあります(こちらは結合テストの位置づけになります)。

まとめ

  • JUnitは、Javaの単体テストの定番。JUnit 5の書き方はJUnit 6でも共通
  • Mavenは junit-jupiter、Gradleは useJUnitPlatform() を忘れずに
  • 基本は @Test + assertEquals(期待値が先)、例外は assertThrows
  • 複数の値で試すなら @ParameterizedTest、依存の差し替えは Mockito、読みやすさは AssertJ
  • テストの値は、正常系・境界値・異常系・端数・0件の観点で選ぶ
  • AIが書いたテストは、期待値が仕様から導かれているかを確認する

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「JUnitの使い方|単体テストの書き方と観点【JUnit 5・6】」

株式会社RJCでは、テストを「品質を確かめ、安心して変更するための仕組み」として大切にしています。こうした知識を仲間と共有しながら、一緒に成長を楽しんでいます。

株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?