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

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

Avatar for Naka Sho Naka Sho
October 13, 2025
16

単体テストの精度を高めるための 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 testFindWithTitle() { // 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 testFindWithEmptyResults() { // 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

    testFindWithNullTitle() { // 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. web service repository persistence integration api batch domain integration integration

    コーディングエージェントの支援 integrationのテスト概念 controller 正しくレスポンスが返ってくるテスト API Requestを行って正しくレスポンスを受け取るテスト
  12. コーディングエージェントの支援 ガイドラインの修正 ### integration test * モックの設定はできるだけ現実的な設定にすること  * 外部APIのリクエストは以下の方法でテストすること: -

    Mockitoを使用したレスポンスのバリエーションのテスト ー> 正しくレスポンスが返ってくるテスト - Wiremockを使用したテスト ー> API Requestを行って正しくレスポンスを受け取るテスト - 2xxの正常レスポンス - レスポンスのバリエーションは一部の正常系だけあればよい - 4xxの異常レスポンス - 5xxの異常レスポンス - タイムアウト - 接続エラー - 不正なレスポンス形式 - parse error - blank
  13. web service repository persistence integration api batch domain コーディングエージェントの支援 persistenceのテスト概念

    controller repository persistence データのパターン 1,SQLが正しいデータのバリエーション 2,一部トランザクション周りのデータの整合性 persistence
  14. コーディングエージェントの支援 環境設定に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の実行に成功
  15. コーディングエージェントの支援 事前データの挿入がちょっと想定外 @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でなくて もよさそう
  16. コーディングエージェントの支援 ガイドラインに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());
  17. コーディングエージェントの支援 ガイドラインの修正 ### 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); のように 具体的にクエリを確認すること * クエリはパラメータバインド済みのものを比較してください。
  18. web service repository persistence integration api batch domain コーディングエージェントの支援 contorollerのテスト概念

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

    controller api web batch ServiceがただしくMockでテストして その後フェーズでIt,St,SATとテストしていく、 価値のあるテストなのか?? Service??
  20. コーディングエージェントの支援 今まで通りの実装 @Test @DisplayName("正常系:Spring書籍検索") void testFindByIsbn_ExistingIsbn_ShouldReturnBook() { // 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); 正しいことが正しいわけではない。 工数と品質のバランスを考えよう
  21. web service repository persistence integration api batch domain コーディングエージェントの支援 contorollerのテスト概念

    controller Controllerは外から中に入っていく世界であり、 UTとITでテスト実施する内容にかぶりがあることが多く感じる。 中の世界のテストが完了していたら、基本はcontroller層以下はMockにせず使えるはず (ブラックボックス、ホワイトボックス、ファットコントローラー、ファットサービスとか関係なく、 APIの結合試験をUT内で実施する つまりITa(内部結合)相当のテスト
  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 @DisplayName("正常系: Spring書籍検索") @Transactional

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

    NGの判断が困難 3.大きい指示は当たり前ですができない a.「これおねがい」の「これ」を理解できるのはまだ人間だけ i.人間でも難しい AI コーディングエージェントの支援 コーディングエージェントをうまく使いこなす AI