R e i - D r e a m
for Laravel
Guest
login

最終投稿日:2026年09月27日

【検証環境】- Windows11 - RockyLinux 8 - JDK 17-LTS - Eclipse 2024-09 R - Wildfly 28.0.1

目次

ユニットテスト

ユニットテストとは

ユニットテスト(単体テスト)とは、プログラムを構成する最小単位(クラスやメソッド・関数)が、
設計通りに単体で正しく動くか検証するテストのことです。
画面を使用する手動(打鍵)テストとの大きな違いは、画面やDBなどの外部環境に依存しない点です。
たとえ画面の構築が終わっていなかったり画面側でエラーが出たりする状態でも、
バックエンドのクラス単体で動作をテストできるため、障害が発生した際も「画面の問題」と「ロジックの問題」を明確に切り分けることができます。
ユニットテストの圧倒的に優れている点は『再現性の高さ』と『デグレ(先祖返り・退行)確認の容易さ』です。
画面打鍵でのテストの場合、例えば結合テストでバグが見つかりプログラムを修正したあと再度テストしようとすると、
DBのデータなどを当時の状態へ完璧に戻す必要があります。
これは実務において非常に困難な場合が多く、データが少し違うだけで「データが原因で結果が変わったのでは?」と疑心暗鬼になりがちです。
一方でユニットテストは、テストケースやデータ(状態)がすでにソースコードとして定義されているため、常に全く同じ環境・条件で一瞬でテストを再実行できます。
問題点としては、クラスの作り次第ではユニットテストが『やりにくい』事もあります。
    ※その場合は打鍵テストに切り替える場合が多いです。
俗に『良いクラスの作りはユニットテストがしやすい事』と言われますが、それを意識したクラス構築を心がけましょう。

pom.xml

pom.xml

<!-- JUnit 5 (Jupiter API & Engine Aggregator) -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>
<!-- Mockito Core (モックオブジェクト作成用) -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.11.0</version>
    <scope>test</scope>
</dependency>
<!-- Mockito JUnit 5 統合用 (@ExtendWith(MockitoExtension.class) を使用する場合) -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>5.11.0</version>
    <scope>test</scope>
</dependency>

バージョン等はご自身の環境に合わせて下さい。
内容の通り『JUnit 5』を使用します。
2017年のリリースとなり、現在(2025年)は『JUnit 6』もリリースされています。
しかし「Java 17以降」でしか動作しないため、今回は『5』での説明とします。
また『JUnit 5』のコードは、基本的に『JUnit 6』でも動作しますので内容的には問題はありません。

テストクラス作成

テストクラスを作成するには、当たり前ですがテスト対象のクラスが必要です。
今回は以下の様なサンプルクラスを作成します。
SampleService.java

package service;

import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import utility.DateUtil;
import dto.SampleDto;
import entity.User;
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Inject;
import logic.SampleLogic;

@Dependent
public class SampleService {
    private static final Logger logger = LoggerFactory.getLogger(SampleService.class);
    
    @Inject
    private SampleDto dto;
    @Inject
    public SampleLogic appLogic;
    
    public String chkUserInfo(String name) {
        logger.info("------------- SampleService.chkUserInfo ---------------");
        dto.setName(name);
        String ymd = DateUtil.getNowToString();
        List<User> list = appLogic.selectUserInfo(dto, ymd);
        if (list != null && list.size() > 0) {
            for (User ent : list) {
                if (ent.getName().equals(name)) {
                    return "ユーザー『" + name + "』は存在します。";
                }
            }
            return "ユーザー『" + name + "』はいません。";
        }
        return "ユーザーは誰もいません。";
    }
}

いきなりこのクラスだけを作成してもシンタックスエラーだと思います。
出来るだけエラー状態(赤下線)は、よろしくないので少し説明します。
○ SampleDto dto
パッケージ『dto』に『SampleDto.java』を作成しています。
プロパティ『private String name;』のみ定義が必要です。
○ DateUtil.getNowToString()
パッケージ『utility』に『DateUtil.java』を作成しています。
静的メソッドとして以下内容としています。
public static String getNowToString() {
    LocalDate date = LocalDate.now();
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
    return date.format(formatter);
}
○ List list = appLogic.selectUserInfo(dto, ymd)
『User』『SampleLogic』クラスを定義しています。
『User』クラスは、パッケージ『entity』にプロパティ『private String name;』を設定します。
『SampleLogic』クラスは、パッケージ『logic』にメソッド『public List<User> selectUserInfo(SampleDto dto, String ymd)』を作成します。
    ※返却は「null」で良いです
○ テストクラス作成
テストクラスを作成しますので、対象プログラム名を右クリックして以下を選択します。
[新規] > [その他] > ダイアログがPOPします。
[Java] > [JUnit] > [JUnit テスト・ケース] > [次へ]
ラジオボタンが「新規 JUnit Jupiter テスト」となっている事
ソース・フォルダーが「jakaruta-pj/src/test/java」になっている事を確認し『完了』を押下します。
すると以下の様なテストクラスが生成されます。
ポイント

『test』フォルダが無い場合は、以下のパスを作成します。
    src/test/java

次にプロジェクトを右クリックし『ビルド・パス(B)』>『ビルドパスの構成(C)』を選択します。
『ソース』タブに『{プロジェクト名}/src/test/java』が存在するか確認してください。
存在しない場合は『フォルダーの追加』ボタンを押下し追加して下さい。

src/test/java/service/SampleServiceTest.java

package service;

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

class SampleServiceTest {
    @Test
    void test() {
        fail("まだ実装されていません");
    }
}

生成されたクラスが『ユニットテストクラス』となります。

テストの実行

現状テストケースがありませんので、関数『void test()』の内容を以下に書き換えて下さい。
    ※『@Test』を付与したメソッドがテストメソッドとなり、複数作成する事も可能です
String hoge = "ほげ";
String foo = "ふ~";
assertNotEquals(hoge, foo);
○ ユニットテスト実行
対象のテストコードを適当な場所で右クリックします。
[実行(R)]を選択すると以下の様なメニューとなりますので『2 JUnitテスト』を選択します。
1 サーバで実行
2 JUnitテスト
3 ...
するとエクリプスの『JUnit』タブがアクティブとなり、緑の横帯が表示されます。
テスト成功画面
    緑はテスト成功を意味します!
試しにテストコードを以下の様に修正して実施してみてください。
assertNotEquals(hoge, foo);
⇒
assertEquals(hoge, foo);
今度は赤くなりましたね。
    当然これはテスト失敗を意味します。
○ テスト失敗時の障害トレース
『JUnit』タブの《障害トレース》に注目して下さい。
障害トレース

org.opentest4j.AssertionFailedError: expected: <ほげ> but was: <ふ~>
    at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
    at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
    at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
    at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
    at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
    at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1145)

    at service.SampleServiceTest.test(SampleServiceTest.java:13)
    at java.base/java.lang.reflect.Method.invoke(Method.java:569)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

重要なのは上記『エラー原因内容』『エラー行数』となります。
明確に『テストは[ほげ]を期待したけど[ふ~]だったよ』と言っています。
そして『SampleServiceTest.java:13』とテストした行数も教えて貰っています。

ユニットテストのライフサイクル

テストクラスでは、事前処理または事後処理として、以下のアノテーションを付与したメソッドを定義できます。
@BeforeAll                  すべてのテストの実行前の処理
@AfterAll                      すべてのテストの実行後の処理
@BeforeEach              各テストの実行前の処理
@AfterEach                 各テストの実行後の処理
『~All』『~Each』で名前が似ていますが、違いは以下となります。
○ 『BeforeAll』『AfterAll』
テストメソッド全体に対して最初(または最後)の1回のみ実行されます。
またこのメソッドは『static』でなければいけません。
○ 『BeforeEach』『AfterEach』
各テストメソッドの最初(または最後)に対して実行されます。
つまり複数のテストメソッドがあれば、都度実行されます。
またこのメソッドは通常(staticではない)のメソッドで構いません。
サンプル

package service;

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;

class SampleServiceTest {
    private static String foo;
    private String hoge;
    @BeforeAll
    static void beforeAll() {
        foo = "ふ~";
    }
    @BeforeEach
    void setUp(TestInfo testInfo) {
        hoge = "ほげ";
        // 実行中のテストメソッド名を取得
        String methodName = testInfo.getTestMethod().get().getName();
        if (methodName.equals("test_02")) {
            hoge = "ふ~";
        }
    }
    @Test
    void test_01() {
        assertEquals(hoge, foo);
    }
    @Test
    void test_02() {
        assertEquals(hoge, foo);
    }
}

サンプルを実行するとテスト全体としては【赤】となり失敗判定となります。
しかし『JUnit』タブの各テストアイコンを見てください。
メソッド名《test_02》だけ成功しているのが確認できるハズです。
これは『BeforeEach』処理で、メソッド名《test_02》の場合変数『hoge』の値を変更しているためです。
今回はテストソースの中で値を定義しましたが、実際は当然開発ソースの内容をテストします。
本章では『JUnit 5』の基本となり、テストソースの自動生成からテスト実行方法までとなります。
○ 注意
クラス『TestInfo』を使用する場合、ビルドエラーになる場合があります。
その場合は『beans.xml』の内容を修正します。
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
        bean-discovery-mode="all">
</beans>
⇒
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
        bean-discovery-mode="annotated">
</beans>
ポイント

『beans.xml』の「bean-discovery-mode」とは、CDI(Contexts and Dependency Injection)がアプリケーション内のクラスをどれだけ自動的にインジェクション対象として認識するかを決める設定です。
《all》は当然全てとなりますが《annotated》は、明示的にCDI用の注釈が付いたクラスのみが対象となります。

そのため、安易に『all ⇒ annotated』とすると、予期せぬビルドエラーとなる場合があります。
これは今まで省略(または忘れていた)注釈を《all》が補完していた場合に発生します。
また《all》の設定でなぜ『TestInfo』がエラーの原因となったかと言うと『TestInfo』をCDIで解決しようとした結果となります。
つまり、CDI解決しようとしたが『TestInfo』には具体的な実装クラスが存在しないためエラーとなるのです。

ログインしてコメントを残そう!!


きっぷる