単体テスト完全ガイド:JUnitから自動化まで - Qiita
分析結果
- カテゴリ
- IT
- 重要度
- 51
- トレンドスコア
- 15
- 要約
- 単体テスト完全ガイド:JUnitから自動化まで #Java - Qiita 9 いいねしたユーザー一覧へ移動 4 X(Twitter)でシェアする Facebookでシェアする はてなブックマークに追加する more_horiz 記事を削除する close 一度削除した記事は復旧できません。 この記事の編集中の下書きも削除されます。 削除してよろしいですか? キャンセル 削除する delete info この記事は最終更新日から1年以上
- キーワード
単体テスト完全ガイド:JUnitから自動化まで #Java - Qiita 9 いいねしたユーザー一覧へ移動 4 X(Twitter)でシェアする Facebookでシェアする はてなブックマークに追加する more_horiz 記事を削除する close 一度削除した記事は復旧できません。 この記事の編集中の下書きも削除されます。 削除してよろしいですか? キャンセル 削除する delete info この記事は最終更新日から1年以上が経過しています。 @ masterpiecehack 単体テスト完全ガイド:JUnitから自動化まで Java JUnit unittest 単体テスト 9 最終更新日 2025年04月05日 投稿日 2025年03月08日 単体テストとCI/CD自動化ガイド 目次 単体テストの基本概念 1.1 単体テストとは 1.2 単体テストの目的 1.3 テスト駆動開発(TDD) 1.4 単体テストの基本原則 単体テストの観点と判断基準 2.1 テスト観点 2.2 テスト判断基準 2.2.1 テストカバレッジ 2.2.2 テスト成功率 2.2.3 テスト品質の評価 2.3 モックとスタブの活用 JUnit活用ガイド 3.1 JUnitの概要 3.2 JUnitの基本的な使い方 3.2.1 テストクラスとテストメソッド 3.2.2 テストライフサイクル 3.2.3 アサーション 3.2.4 パラメータ化テスト 3.3 モックフレームワークの活用 3.3.1 外部APIに依存するケース 3.3.2 Mockitoを使ったテスト 3.4 テストスイートの構成 JUnit完全チートシート 4.1 基本アノテーション 4.2 アサーションメソッド 4.3 パラメータ化テスト 4.4 条件付きテスト実行 4.5 ネストされたテスト 4.6 動的テスト 4.7 Mockitoとの連携 4.8 Spring Bootとの連携 Mockito完全チートシート 5.1 Mockito基本機能一覧 5.2 高度なモック化テクニック 5.3 Mockitoのベストプラクティス Spring Bootのテスト詳細ガイド 6.1 Spring Bootテストアノテーション概要 6.2 主要テストアノテーションの使い分け 6.3 各アノテーションの実装例と設定オプション 6.3.1 @SpringBootTest 6.3.2 @WebMvcTest 6.3.3 @DataJpaTest テストデータベース設定と管理 7.1 テスト用データベースの選択と構成 7.1.1 H2インメモリDB設定 7.1.2 Testcontainersによる実DBテスト 7.2 テストデータのセットアップ手法 7.2.1 @Sqlアノテーションの活用 7.2.2 TestEntityManagerの活用 7.3 トランザクション管理のベストプラクティス 実践的なテストケース設計手法 8.1 テスト分析手法 8.1.1 同値分割 8.1.2 境界値分析 8.2 効率的なテストデータ生成 8.2.1 テストデータビルダーパターン 8.2.2 テストデータファクトリー 単体テスト自動化の概念 9.1 継続的インテグレーション(CI)とは 9.2 単体テスト自動化のメリット 9.3 Jenkins概要 9.4 Jenkins + JUnitによる自動化フロー 9.5 Jenkinsパイプラインの構成 9.6 他のCI/CDツール CI/CD自動化完全チートシート 10.1 Jenkins基本コマンド 10.2 Jenkins環境変数 10.3 Jenkinsfileスニペット集 10.4 GitHub Actions 10.5 GitLab CI/CD 10.6 CircleCI 10.7 AWS CodePipeline 10.8 テスト自動化のベストプラクティス レガシーコードへのテスト導入戦略 11.1 依存性が高いコードのテスト手法 11.2 段階的なテスト導入アプローチ 11.3 レガシーコードのテストパターン テスト品質メトリクスとダッシュボード構築 12.1 テスト品質の主要メトリクス 12.2 SonarQubeとの連携 12.3 テスト品質ダッシュボードの構築 よくある問題とトラブルシューティング 13.1 JUnitに関する問題 13.2 単体テスト自動化に関する問題 参考資料とリソース 14.1 公式ドキュメント 14.2 書籍 14.3 オンラインリソース 1. 単体テストの基本概念 1.1 単体テストとは 単体テスト(Unit Testing)とは、ソフトウェア開発における最小単位のテストで、個々のモジュール、関数、クラスなどが正しく動作することを確認するテスト手法です。 単体テストの特徴: コードの最小単位(通常は関数やメソッド)をテスト対象とする 外部依存(データベース、ファイルシステム、API等)を分離して実施 自動化が可能で繰り返し実行できる 開発の早い段階で問題を発見できる 1.2 単体テストの目的 バグの早期発見 :機能実装直後にテストを書き、実行することで、バグを早期に発見できます リグレッションの防止 :既存の機能に影響を与えずに新機能を追加できることを確認できます 設計の改善 :テストしやすいコードを書くことで、結合度を下げ、凝集度を高める設計が促進されます ドキュメントとしての役割 :テストコードは、プロダクションコードがどのように動作すべきかを示す生きたドキュメントになります 安全なリファクタリング :テストがあることで、コードを安全に改善できます 1.3 テスト駆動開発(TDD) テスト駆動開発(Test-Driven Development)は、単体テストを中心とした開発手法です。 TDDのサイクル(Red-Green-Refactor): Red :最初に失敗するテストを書く Green :テストが通るように最小限のコードを実装する Refactor :テストが通ることを確認しながらコードをリファクタリングする 1.4 単体テストの基本原則 高速性(Fast) :テストは素早く実行できること 独立性(Isolated/Independent) :テストは他のテストに依存せず、どの順番で実行しても同じ結果になること 繰り返し可能(Repeatable) :何度実行しても同じ結果が得られること 自己検証(Self-validating) :テスト結果は自動的に判定され、手動確認が不要であること 適時性(Timely) :テスト対象のコードを書く前か直後にテストを書くこと これらの頭文字を取って「FIRST」原則とも呼ばれます。 2. 単体テストの観点と判断基準 2.1 テスト観点 単体テストを設計する際の主な観点は以下の通りです: 正常系テスト 正常な入力に対して期待通りの結果を返すか 境界値(最小値、最大値など)での正常動作 パフォーマンス要件を満たすか 異常系テスト 無効な入力に対して適切なエラー処理が行われるか 境界外の値に対する動作 例外処理の検証 エッジケース null値、空の配列・文字列 整数の最大値・最小値 日付の境界(閏年など) 極端に大きいデータ、小さいデータ ビジネスロジック 業務要件に基づいた条件分岐 複雑な計算ロジック ステータス変更の検証 2.2 テスト判断基準 2.2.1 テストカバレッジ テストカバレッジは、テストによってどれだけのコードが実行されたかを示す指標です。業界では以下のような表記も使われます: 主なカバレッジメトリクス: ライン(行)カバレッジ (C0) テストによって実行されたコードの行数の割合 一般的な目標値:70-80%以上 「ステートメントカバレッジ」とも呼ばれます 具体例: public int calculateDiscount ( int price , boolean isPremiumCustomer ) { int discount = 0 ; // この行は実行された if ( isPremiumCustomer ) { discount = price * 20 / 100 ; // テストでisPremiumCustomer=trueの場合のみこの行が実行された } else { discount = price * 10 / 100 ; // テストでisPremiumCustomer=falseの場合のみこの行が実行された } return discount ; // この行は実行された } テストケース1: calculateDiscount(1000, true) → 200が返る テストケース2: calculateDiscount(1000, false) → 100が返る この場合、すべての行が少なくとも1回実行されているため、ラインカバレッジは100%です。 分岐カバレッジ (C1) if文やswitch文などの条件分岐がテストで網羅されている割合 一般的な目標値:80%以上 「ブランチカバレッジ」とも呼ばれます 具体例: public String checkTemperature ( int temp ) { if ( temp < 0 ) { return "凍結注意" ; // 分岐1 } else if ( temp < 15 ) { return "肌寒い" ; // 分岐2 } else if ( temp < 25 ) { return "快適" ; // 分岐3 } else { return "暑い" ; // 分岐4 } } テストケース1: checkTemperature(-5) → "凍結注意"が返る テストケース2: checkTemperature(20) → "快適"が返る この場合、4つの分岐のうち2つしかテストされていないため、分岐カバレッジは50%です。 条件カバレッジ (C2) 複合条件(AND、OR)の各部分がテストで評価されている割合 例: if (a && b) の場合、a=true/false、b=true/falseの組み合わせ 具体例: public boolean isEligibleForDiscount ( int age , boolean isStudent ) { if ( age < 18 || isStudent ) { return true ; } return false ; } 条件カバレッジで完全に網羅するには、以下の組み合わせをテストする必要があります: age < 18 が true、isStudent が true age < 18 が true、isStudent が false age < 18 が false、isStudent が true age < 18 が false、isStudent が false テストケース1: isEligibleForDiscount(16, true) → trueが返る (age < 18 = true, isStudent = true) テストケース2: isEligibleForDiscount(20, false) → falseが返る (age < 18 = false, isStudent = false) この場合、4つの条件組み合わせのうち2つしかテストされていないため、条件カバレッジは50%です。 パスカバレッジ (C3) プログラム内の可能なすべての実行パスが網羅されている割合 最も厳しいカバレッジ基準 具体例: public String categorizeOrder ( boolean isPriority , boolean isExpensive , boolean isInternational ) { String category = "Standard" ; if ( isPriority ) { category = "Priority" ; } if ( isExpensive ) { category += " Valuable" ; } if ( isInternational ) { category += " International" ; } return category ; } この関数には2³ = 8つの異なる実行パスがあります。パスカバレッジ100%を達成するには、すべてのパスをテストする必要があります。 2.2.2 テスト成功率 テスト成功率は、全テストケースのうち成功したテストの割合を示します。 業界標準の判断基準: 合格基準 :100%(すべてのテストが成功) 許容基準 :テスト失敗は許容されないのが原則 ただし、初期開発フェーズやレガシーコードのリファクタリング時には、段階的な改善を目標にすることもあります。 2.2.3 テスト品質の評価 カバレッジだけでなく、テスト自体の品質も重要です: テストの独立性 各テストは他のテストに依存せず、単独で実行できるか テストの信頼性 何度実行しても同じ結果が得られるか(フラキーテストがないか) テストの可読性 テストの意図が明確か テスト名が目的を表しているか アサーションの品質 単一責任の原則に従っているか 適切なアサーションを使用しているか テストの実行速度 テストスイート全体が妥当な時間内に完了するか 2.3 モックとスタブの活用 外部依存がある場合、テスト対象を分離するためにモックやスタブを使用します: スタブ(Stub) 外部依存の代わりに使用する単純な実装 特定の入力に対して決まった出力を返す モック(Mock) 外部依存の代わりに使用する高度なテストダブル メソッド呼び出しの検証や動的な振る舞いの制御が可能 スパイ(Spy) 実際のオブジェクトの動作を記録する メソッド呼び出し回数や引数を検証できる フェイク(Fake) 本物と同様の動作をする軽量な実装 例:インメモリデータベース ダミー(Dummy) 単にパラメータを満たすためだけに使用するオブジェクト 実際には使用されない 3. JUnit活用ガイド 3.1 JUnitの概要 JUnitは、Javaプログラム用の単体テストフレームワークの代表的な存在です。JUnit 5は次の3つの主要コンポーネントで構成されています: JUnit Platform :テスト実行のための基盤 JUnit Jupiter :JUnit 5のための新しいプログラミングモデルとエクステンション JUnit Vintage :JUnit 3および4との後方互換性のためのモジュール 3.2 JUnitの基本的な使い方 ここでは、もっと具体的なシナリオを想定してJUnitの基本的な使い方を説明します。 3.2.1 テストクラスとテストメソッド 例えば、オンラインショップの注文処理システムを開発していると想定します。このシステムには以下のような「OrderProcessor」クラスがあるとします: public class OrderProcessor { // 注文を処理して割引を適用する public double calculateFinalPrice ( Order order ) { double price = order . getBasePrice (); // 5000円以上で5%割引 if ( price >= 5000 ) { price = price * 0.95 ; } // プレミアム会員はさらに10%割引 if ( order . getCustomer (). isPremium ()) { price = price * 0.9 ; } // 送料追加(北海道・沖縄は追加送料) String prefecture = order . getShippingAddress (). getPrefecture (); if ( "北海道" . equals ( prefecture ) || "沖縄県" . equals ( prefecture )) { price += 500 ; } else { price += 300 ; } return price ; } } このOrderProcessorクラスをテストするためのJUnitテストクラスを作成します: import org.junit.jupiter.api.Test ; import org.junit.jupiter.api.BeforeEach ; import static org . junit . jupiter . api . Assertions .*; public class OrderProcessorTest { private OrderProcessor processor ; private Customer regularCustomer ; private Customer premiumCustomer ; private Address tokyoAddress ; private Address hokkaidoAddress ; @BeforeEach void setUp () { // テストごとに新しいインスタンスを準備 processor = new OrderProcessor (); regularCustomer = new Customer ( "田中太郎" , false ); // 一般会員 premiumCustomer = new Customer ( "佐藤花子" , true ); // プレミアム会員 tokyoAddress = new Address ( "東京都" , "新宿区" , "1-1-1" ); hokkaidoAddress = new Address ( "北海道" , "札幌市" , "2-2-2" ); } @Test void testRegularCustomerWithSmallOrderInTokyo () { // 通常顧客が東京で3000円の注文を行うケース Order order = new Order ( 3000 , regularCustomer , tokyoAddress ); double finalPrice = processor . calculateFinalPrice ( order ); // 3000円 + 送料300円 = 3300円になるはず assertEquals ( 3300 , finalPrice , "通常顧客の小額注文(東京)の計算が正しくありません" ); } @Test void testPremiumCustomerWithLargeOrderInHokkaido () { // プレミアム顧客が北海道で10000円の注文を行うケース Order order = new Order ( 10000 , premiumCustomer , hokkaidoAddress ); double finalPrice = processor . calculateFinalPrice ( order ); // 10000円 - 5%割引 = 9500円 // 9500円 - 10%割引 = 8550円 // 8550円 + 送料500円 = 9050円になるはず assertEquals ( 9050 , finalPrice , "プレミアム顧客の大口注文(北海道)の計算が正しくありません" ); } } 3.2.2 テストライフサイクル JUnitのテストライフサイクルを制御するアノテーション: import org.junit.jupiter.api.* ; public class LifecycleDemoTest { @BeforeAll static void setUpAll () { // テストクラス全体の前に1回だけ実行 System . out . println ( "BeforeAll" ); } @BeforeEach void setUp () { // 各テストメソッドの前に実行 System . out . println ( "BeforeEach" ); } @Test void testMethod1 () { System . out . println ( "Test method 1" ); } @Test void testMethod2 () { System . out . println ( "Test method 2" ); } @AfterEach void tearDown () { // 各テストメソッドの後に実行 System . out . println ( "AfterEach" ); } @AfterAll static void tearDownAll () { // テストクラス全体の後に1回だけ実行 System . out . println ( "AfterAll" ); } } 3.2.3 アサーション JUnit Jupiterは多様なアサーションメソッドを提供しています: import org.junit.jupiter.api.Test ; import static org . junit . jupiter . api . Assertions .*; class AssertionsDemoTest { @Test void standardAssertions () { assertEquals ( 2 , 1 + 1 ); // 値の比較 assertTrue ( 1 < 2 ); // 条件が真かどうか assertFalse ( 1 > 2 ); // 条件が偽かどうか } @Test void groupedAssertions () { // グループ化されたアサーション(すべて実行され、すべての失敗がレポートされる) assertAll ( "person" , () -> assertEquals ( "John" , person . getFirstName ()), () -> assertEquals ( "Doe" , person . getLastName ()) ); } @Test void exceptionTesting () { // 例外発生の検証 Exception exception = assertThrows ( ArithmeticException . class , () -> { int result = 1 / 0 ; }); assertEquals ( "/ by zero" , exception . getMessage ()); } @Test void timeoutNotExceeded () { // タイムアウトの検証 assertTimeout ( Duration . ofMillis ( 100 ), () -> { // 100ミリ秒以内に完了する処理 }); } } 3.2.4 パラメータ化テスト 同じテストロジックで異なる入力値をテストする場合に便利です: import org.junit.jupiter.params.ParameterizedTest ; import org.junit.jupiter.params.provider.CsvSource ; import org.junit.jupiter.params.provider.ValueSource ; class ParameterizedTestsDemo { @ParameterizedTest @ValueSource ( int