Upgrade to Pro — share decks privately, control downloads, hide ads and more …

単体テストの精度を高めるための guideline

Avatar for Naka Sho Naka Sho
October 13, 2025
77

単体テストの精度を高めるための guideline

単体テストの精度を高めるための
guideline

Avatar for Naka Sho

Naka Sho

October 13, 2025
Tweet

Transcript

  1. はじめに 本日話すこと 世の中、コーディングエージェント戦線に突入しています。 ツール名 主な機能 対応エディタ Cursor AIとの対話型コーディング、コードベース全体の理解 Cursor Junie

    IDE統合型のコーディング支援 JetBrains IDEs GitHub Copilot Agent リポジトリ全体の理解、イシューからPR作成、ブラウザベース開発環境 Webベース・統合型 GitHub Copilot Coding Agent Mode 仮想マシン起動、環境設定、マルチステップタスク実行 VS Code Devin 自律的な開発、デプロイ、デバッグ Webベース・統合型 Cline 自律的コーディングタスク実行 VS Code Claude Code ターミナルからの直接的なコーディングタスク委任 コマンドライン
  2. はじめに 本日話すこと 重要な5W1H Who(誰が) :開発者 What(何を) :単体テスト(UT) When(いつ) :開発時 Where(どこで)

    :IDE or cli Why(なぜ) :効率的な開発を行うため How(どのように) :コーディングエージェントにお任せ ← 難しい
  3. 各種テストフェーズ 要求分析 → 受け入れテスト(UAT) 基本設計 → システムテスト(STa,STb) 機能設計 → 統合テスト(ITa,ITb)

    詳細設計 → 単体テスト(UT) 単体テスト(以降UTと表します)は、ソフトウェア開発において最も基本的なテスト手法であり、 個々のプログラムやモジュールが設計通りに動作するかを確認します。これにより、コードの品質を 確保し、バグを早期に発見することができます。UTは主に詳細設計書やコードに基づいて実施され ます。 単体テストについて テストフェーズについて
  4. 単体テストについて テストの粒度について ユニットテスト (Unit Test / UT) 最小単位のコンポーネントが、単体で正しく動作するかを検証します。 インテグレーションテスト (Integration

    Test / IT) 複数のユニットを組み合わせて、それらの連携が正しく行われるかを検証します。 E2E(End-to-End) 実際のユーザーのシナリオに沿って最初から最後まで正しく動作するかを検証します。
  5. 単体テストについて 単体テストの現実的課題 技術的課題 複雑な条件分岐の組み合わせ レガシーコードがテスト困難、再現困難 ← テストがあったりなかったり コスト・時間的課題 テストコード量が実装コードの2-3倍 仕様変更時のメンテナンス負荷増大、レビューの負荷増大

    スキルの個人差、モチベーション低下 組織・プロジェクト管理課題 品質vs納期のトレードオフ、優先度判断の困難さ 投資対効果の見極め困難 組織の圧力(ゴッドフォール、ゴッドプレス)
  6. 単体テストについて 単体テストの実践的な対処法 段階的アプローチ 現実的な目標設定80% チーム合意による基準の設定 完璧主義からの脱却 重要度に基づく優先付け 効率化 テストの自動生成 ←

    コーディングエージェントの出番 複雑度の高い部分のリファクタリング ←TODO:リファクタリングのためにテスト を作成する 組織的対応 チームスキルのための共有・研修 ペアプログラミングによる知識伝達
  7. コーディングエージェントの支援 テスト実装内容の確認 BookServiceImplTest.javaを確認する。 @Service @RequiredArgsConstructor public class BookServiceImpl implements BookService

    { private final BookRepository bookRepository; @Override public List<Book> find(String title) { return bookRepository.find(title); } }
  8. コーディングエージェントの支援 テスト実装内容の確認 @Test void test_1件以上存在すること() { // Arrange String title

    = "Test"; List<Book> expectedBooks = Arrays.asList(book1, book2); when(bookRepository.find(title)).thenReturn(expectedBooks); // Act List<Book> result = bookService.find(title); // Assert assertNotNull(result); assertEquals(2, result.size()); assertEquals(expectedBooks, result); // Verify interactions verify(bookRepository).find(title);
  9. コーディングエージェントの支援 テスト実装内容の確認 @Test void test_0件であること() { // Arrange String title

    = "Nonexistent"; when(bookRepository.find(title)).thenReturn(Collections.emptyList()); // Act List<Book> result = bookService.find(title); // Assert assertNotNull(result); assertTrue(result.isEmpty()); // Verify interactions verify(bookRepository).find(title); }
  10. null検索でリストが返ってくる? テストポリシーが何も書いていないの でテストの精度が低い and 仕様が不明なのでテストが正しいか わからない コーディングエージェントの支援 テスト実装内容の確認 @Test void

    test_nullで検索() { // Arrange List<Book> expectedBooks = Arrays.asList(book1, book2); when(bookRepository.find(null)).thenReturn(expectedBooks); // Act List<Book> result = bookService.find(null); // Assert assertNotNull(result); assertEquals(2, result.size()); assertEquals(expectedBooks, result); // Verify interactions verify(bookRepository).find(null); }
  11. external コーディングエージェントの支援 externalのテスト概念 - 正常系 - 4xxの異常レスポンス - 5xxの異常レスポンス -

    接続エラー - タイムアウト API処理開始 API処理終了 4xx系は基本リクエストエラー 5xx系のうち、先方側のAPIで 正常に処理できたのか 異常になったのかわからない 冪等性 正常 or 異常
  12. コーディングエージェントの支援 ガイドラインの修正 ### external test * モックの設定はできるだけ現実的な設定にすること  * 外部APIのリクエストは以下の方法でテストすること: -

    Mockitoを使用したレスポンスのバリエーションのテスト ー> 正しくレスポンスが返ってくるテスト - Wiremockを使用したテスト ー> API Requestを行って正しくレスポンスを受け取るテスト - 2xxの正常レスポンス - レスポンスのバリエーションは一部の正常系だけあればよい - 4xxの異常レスポンス - 5xxの異常レスポンス - タイムアウト - 接続エラー - 不正なレスポンス形式 - parse error - blank
  13. コーディングエージェントの支援 環境設定に4時間くらいかかった。 。 。 @MybatisTest @ContextConfiguration(classes = { DatabaseConfig.class })

    @EnableConfigurationProperties @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class StackedUserRecordCustomMapperTest { @Autowired StackedUserRecordCustomMapper stackedUserRecordCustomMapper; @Test public void test() { Optional<StackedUserRecord> byUserId = stackedUserRecordCustomMapper.findByUserId(userId); } SQLの実行に成功
  14. コーディングエージェントの支援 事前データの挿入がちょっと想定外 @Test @DisplayName("正常系:存在するISBNで本が取得できること") void testFindByIsbn_ExistingIsbn_ShouldReturnBook() { // Arrange -

    まず本を挿入 StackedBookRecord newBook = new StackedBookRecord(); newBook.setTitle("テスト用の本"); stackedBookCustomMapper.insert(newBook); // Act Optional<StackedBookRecord> result = stackedBookCustomMapper.findByIsbn(uniqueIsbn); // Assert Assertions.assertTrue(result.isPresent()); Assertions.assertEquals(uniqueIsbn, result.get().getIsbn()); } findByIsbnのテス トの場合、事前投入 はMapperでなくて もよさそう
  15. コーディングエージェントの支援 ガイドラインにDatabaseSetup使ってくださいと記載 @Test @DisplayName("正常系:テストデータのISBNで本が取得できること") @Sql(scripts = "/test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)

    → xmlでない @Sql(scripts = "/cleanup-data.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void testFindByIsbn_ExistingIsbn_ShouldReturnBook() { // Arrange Long existingIsbn = 9784798142470L; // 存在するISBN // Act Optional<StackedBookRecord> result = stackedBookCustomMapper.findByIsbn(existingIsbn); // Assert Assertions.assertTrue(result.isPresent()); StackedBookRecord book = result.get(); Assertions.assertEquals(existingIsbn, book.getIsbn());
  16. コーディングエージェントの支援 ガイドラインの修正 ### repository test * Mokitoでテストしてください * トランザクションが関わる処理はDBUnitしてください ###

    persistence test * DBUnitを使用してください - DBのデータはSQLで事前に投入してください * CRUD - Rは取得できるパターン、取得できないパターンをテストすること - CUDは変更されるパターン、変更されないパターンをテストすること * 例外処理として、DBエラーパターンをMockitoを使用してテストすること * 実行したSQLが想定通りのSQLのなのかSQL構文のテストすること ー>  (ここはやりすぎ) * Assertions.assertEquals("select stacked_user_id from stacked_user where user_id = 1", executedSql); のように 具体的にクエリを確認すること * クエリはパラメータバインド済みのものを比較してください。
  17. web service repository persistence external api batch domain コーディングエージェントの支援 contorollerのテスト概念

    controller このあたりのテストは説明する必要なさそうなので 省略します
  18. web service repository persistence external api batch domain コーディングエージェントの支援 contorollerのテスト概念

    controller api web batch ServiceをMockでテストして その後フェーズでIt,St,UATとテストしていく、 価値のあるテストなのか?? Service??
  19. コーディングエージェントの支援 contorollerのテスト @Test void test_書籍検索() { // Arrange List<Book> bookList

    = List.of(xxx,yyy,zzz); when(BookService.search(title)).thenReturn(bookList); // Act List<Book> result = SearchController.search(title); // Assert assertEquals(result, bookList); // Verify interactions verify(BookService).search(title); } 正しいことが正しいわけではない。 工数と品質のバランスを考えよう
  20. web service repository persistence integration api batch domain コーディングエージェントの支援 contorollerのテスト概念

    controller Controllerは外から中に入っていく世界であり、 UTとITでテスト実施する内容にかぶりがあることが多く感じる。 中の世界のテストが完了していたら、基本はcontroller層以下はMockにする必要なし。 (ブラックボックス、ホワイトボックス、ファットコントローラー、ファットサービスとか関係なく、 ITa(内部結合)相当のテストをやると品質・工数削減につながるのでは?
  21. コーディングエージェントの支援 各テストレベルの整理 区分 目的 方針形式 検出欠陥 UT Unit Test 単体の正しい振る舞い・分岐網羅

    ロジック/境界値/例外 開発者 全Mock ロジック/境界値/例外 Ita Component / Integration Test モジュール間結合テスト インタフェース不整合 / データフォーマット 開発者(一部QA) 外部依存は原則 Mock、 内部は実物 インタフェース不整合 / データフォーマット Itb System Integration Test (Internal + External Interface handshake) システム内全体結合/トランザクション整合 外部とインターフェース接続確認 QA / 開発 外部Stub or 仮想化開始 シナリオエラー / トラ ンザクション不整合 Sta System Test/Functional Test システム機能要件の網羅的検証 仕様漏れ / 性能 閾値初期確認 / 使い勝手初期 QA主導(開発支援) 高忠実度Stub+一部実機 仕様漏れ / 性能 閾値 Stb End-to-End / Regression / Performance / Spike Tests 業務フロー/回帰/非機能(負荷・スパイク含む) 業務上の整合 / 回帰欠陥 / 実運用負荷 QA / ビジネス側支援 可能な限り実機 業務上の整合 / 回帰欠 陥 / 実運用負荷 UAT User Acceptance Test ビジネス価値/受入基準の最終確認 要件誤解 / 受入条件未達 / 運用性 ビジネスユーザ / プロダクトオーナ 原則実機 要件誤解 / 受入条件未 達 / 運用性
  22. コーディングエージェントの支援 ガイドラインの修正 ### controller test * 統合テスト(Integration Test)で実装してください - SpringBootTest

    + WebTestClientを使用してください - SpringBootTestの起動時間を少なくため、起動設定は共通化してください - IntegrationTestBaseを継承してWireMock設定を共通化してください(推奨) - WireMockサーバーの起動・停止が自動化される - 外部API URLが自動的にWireMockサーバーに設定される - 各テストはstubFor()によるレスポンス定義のみに集中できる - DBのデータはSQLで事前に投入してください - DB依存の可能性があるので@Transactionalを必ずつけてください * 実現不可能なテストはMokitoを利用してください
  23. コーディングエージェントの支援 ガイドラインの修正 @Autowired private WebTestClient webTestClient; @Test @Transactional @Sql(scripts =

    "/test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void test_Spring書籍検索() { webTestClient.get() .uri("/search?title=Spring") .exchange() .expectStatus().isOk().expectBodyList(SearchResponse.class).hasSize(1) .consumeWith(result -> { ... 比較していく処理
  24. 1.  明確な指示と結果を予測できる作業はかなり効率が良くなる a.単体テストとか当てはまりやすい気がする i.ガイドラインを細かく記載する。 1.明確な指示をする。 (一部とかやめる) 2.「お願い」だけでできるのは人間だけ 2.人間でわからないことは、AIがわかるわけがない。 a.知識があるので教えてもらえる

    i.最適かどうかOK or NGの判断が困難 3.正しいことが正しいわけじゃない a.工数と品質のバランスを考えよう b.アンチパターンも選択することある AI コーディングエージェントの支援 コーディングエージェントをうまく使いこなす AI Playwrightとか使うと E2Eもある程度自動化できる