Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
単体テストの精度を高めるための guideline
Search
Naka Sho
October 13, 2025
0
77
単体テストの精度を高めるための guideline
単体テストの精度を高めるための
guideline
Naka Sho
October 13, 2025
Tweet
Share
More Decks by Naka Sho
See All by Naka Sho
Javaはレガシーではない!
shogonakao
0
14
型安全性で考えること
shogonakao
0
11
アプリケーションログをs3に転送するとき個人情報気をつけてますか?
shogonakao
0
10
コーディングエージェントと 筋トレ
shogonakao
0
61
SpringBootでAPI開発
shogonakao
0
140
エキサイトブログ刷新に向けて
shogonakao
0
120
【エキサイトブログリビルド】Spring Boot × MyBatis × FreeMarker を使って、データベースの接続先を安全に変更します。
shogonakao
1
790
Featured
See All Featured
The World Runs on Bad Software
bkeepers
PRO
72
11k
Bash Introduction
62gerente
615
210k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
10
640
Done Done
chrislema
186
16k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Six Lessons from altMBA
skipperchong
29
4k
BBQ
matthewcrist
89
9.9k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
36
6.1k
Rails Girls Zürich Keynote
gr2m
95
14k
Scaling GitHub
holman
463
140k
Fireside Chat
paigeccino
41
3.7k
Transcript
単体テストの精度を高めるための guideline 中尾正剛 https://x.com/web_shogo_nakao
はじめに 本日話すこと 単体テストにフォーカスして、開発効率と品質をどのように向上させるかについて 説明します。 これはあくまで私個人の意見や考えであって、公式な見解や他者の意見ではありません。 単体テストは、個々のコンポーネントやモジュールが正しく機能することを確認するための テストです。
はじめに 本日話すこと 世の中、コーディングエージェント戦線に突入しています。 ツール名 主な機能 対応エディタ 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 ターミナルからの直接的なコーディングタスク委任 コマンドライン
はじめに 本日話すこと ワークスロップ問題が浮き彫りに出てきている 「ワークスロップ」とは、AI生成されたコンテンツのうち、一見洗練されて見えるものの、 実質的な内容に乏しく、タスクを意味のある形で前進させることができない仕事のことを 指す。 体裁は整っているが内容が空疎なプレゼンテーション資料 専門性のない人が作成した、もっともらしく見える学術論文の要約 構造化されているが、プロジェクトの重要な文脈が欠落した長大なレポート 動作はするが、実際の要件を満たしていないコード
はじめに 本日話すこと 重要な5W1H Who(誰が) :開発者 What(何を) :単体テスト(UT) When(いつ) :開発時 Where(どこで)
:IDE or cli Why(なぜ) :効率的な開発を行うため How(どのように) :コーディングエージェントにお任せ ← 難しい
各種テストフェーズ 要求分析 → 受け入れテスト(UAT) 基本設計 → システムテスト(STa,STb) 機能設計 → 統合テスト(ITa,ITb)
詳細設計 → 単体テスト(UT) 単体テスト(以降UTと表します)は、ソフトウェア開発において最も基本的なテスト手法であり、 個々のプログラムやモジュールが設計通りに動作するかを確認します。これにより、コードの品質を 確保し、バグを早期に発見することができます。UTは主に詳細設計書やコードに基づいて実施され ます。 単体テストについて テストフェーズについて
単体テストについて テストの粒度について ユニットテスト (Unit Test / UT) 最小単位のコンポーネントが、単体で正しく動作するかを検証します。 インテグレーションテスト (Integration
Test / IT) 複数のユニットを組み合わせて、それらの連携が正しく行われるかを検証します。 E2E(End-to-End) 実際のユーザーのシナリオに沿って最初から最後まで正しく動作するかを検証します。
単体テストについて テストの粒度の傾向 テストケースの数は、以下の傾向があります。 ユニットが最も多い E2Eが最も少ない 「忠実性」とは、物事や状況がどれだけ正確に再現または表現されているかを示す尺度。 UTを増やしても現実的な要件定義を満たしているかとは別であるということ 理想 現実 アイスコーン型
単体テストについて ユニットの量を増やす、かつ、忠実性を高くしたい
ユニット インテグレーション E2E 手動テスト スクエア型 単体テストについて 逆に考えると、手動のテストしっかりできている ユニットテストを増やせば増やした量だけ、忠実性の底上げができる 現実 未来
単体テストについて 単体テストの現実的課題 技術的課題 複雑な条件分岐の組み合わせ レガシーコードがテスト困難、再現困難 ← テストがあったりなかったり コスト・時間的課題 テストコード量が実装コードの2-3倍 仕様変更時のメンテナンス負荷増大、レビューの負荷増大
スキルの個人差、モチベーション低下 組織・プロジェクト管理課題 品質vs納期のトレードオフ、優先度判断の困難さ 投資対効果の見極め困難 組織の圧力(ゴッドフォール、ゴッドプレス)
単体テストについて 単体テストの実践的な対処法 段階的アプローチ 現実的な目標設定80% チーム合意による基準の設定 完璧主義からの脱却 重要度に基づく優先付け 効率化 テストの自動生成 ←
コーディングエージェントの出番 複雑度の高い部分のリファクタリング ←TODO:リファクタリングのためにテスト を作成する 組織的対応 チームスキルのための共有・研修 ペアプログラミングによる知識伝達
単体テストについて コーディングエージェントを使用する 技術的課題 レガシーコードがテスト困難、再現困難 テストの自動生成 ← コーディングエージェントの出番 複雑度の高い部分のリファクタリングしたい ←次のフェーズ
コーディングエージェントの支援 購入した本を管理するWebアプリを実験対象に
web service repository persistence external api batch domain コーディングエージェントの支援 構成の説明
controller
コーディングエージェントの支援 単体テストがないので書いてください。 単体テストがないので書いてください。 BookServiceImplTest.java → こちらで例に指示を出す。
コーディングエージェントの支援 テスト実装内容の確認 BookServiceImplTest.javaを確認する。 @Service @RequiredArgsConstructor public class BookServiceImpl implements BookService
{ private final BookRepository bookRepository; @Override public List<Book> find(String title) { return bookRepository.find(title); } }
コーディングエージェントの支援 テスト実装内容の確認 @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);
コーディングエージェントの支援 テスト実装内容の確認 @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); }
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); }
web service repository persistence external api batch domain コーディングエージェントの支援 externalのテスト概念
controller repositoryとexternalが別れているの はまた今度説明します
external コーディングエージェントの支援 externalのテスト概念 API処理開始 API処理終了
external コーディングエージェントの支援 externalのテスト概念 - 接続エラー - タイムアウト API処理開始 API処理終了
external コーディングエージェントの支援 externalのテスト概念 - 正常系 - 4xxの異常レスポンス - 5xxの異常レスポンス -
接続エラー - タイムアウト API処理開始 API処理終了 4xx系は基本リクエストエラー 5xx系のうち、先方側のAPIで 正常に処理できたのか 異常になったのかわからない 冪等性 正常 or 異常
コーディングエージェントの支援 ガイドラインの修正 ### external test * モックの設定はできるだけ現実的な設定にすること * 外部APIのリクエストは以下の方法でテストすること: -
Mockitoを使用したレスポンスのバリエーションのテスト ー> 正しくレスポンスが返ってくるテスト - Wiremockを使用したテスト ー> API Requestを行って正しくレスポンスを受け取るテスト - 2xxの正常レスポンス - レスポンスのバリエーションは一部の正常系だけあればよい - 4xxの異常レスポンス - 5xxの異常レスポンス - タイムアウト - 接続エラー - 不正なレスポンス形式 - parse error - blank
コーディングエージェントの支援 再度、お願いする
web service repository persistence external api batch domain コーディングエージェントの支援 persistenceのテスト概念
controller
persistence コーディングエージェントの支援 persistenceのテスト概念 SQL処理開始 SQL処理終了
コーディングエージェントの支援 テスト実施するが全くダメ。 。 。 integrationのテストのようにうまくいかない。 。 。
コーディングエージェントの支援 結論から言うと、何回やっても駄目だった どうやらmodule化して分解しすぎて全く動かないようだ。 。 。 申し訳ないです。 分けすぎはよくない。 とりあえず1ケースだけ手動で設定し、以降のテストケースの作成はお任せしようと考えた
コーディングエージェントの支援 環境設定に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の実行に成功
コーディングエージェントの支援 1ケースさえ作れば、一気に作ってくれた ケースに問題はなさそうだった
コーディングエージェントの支援 事前データの挿入がちょっと想定外 @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でなくて もよさそう
コーディングエージェントの支援 ガイドラインに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());
コーディングエージェントの支援 ガイドラインの修正 ### 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); のように 具体的にクエリを確認すること * クエリはパラメータバインド済みのものを比較してください。
web service repository persistence external api batch domain コーディングエージェントの支援 contorollerのテスト概念
controller このあたりのテストは説明する必要なさそうなので 省略します
web service repository persistence external api batch domain コーディングエージェントの支援 contorollerのテスト概念
controller api web batch ServiceをMockでテストして その後フェーズでIt,St,UATとテストしていく、 価値のあるテストなのか?? Service??
コーディングエージェントの支援 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); } 正しいことが正しいわけではない。 工数と品質のバランスを考えよう
web service repository persistence integration api batch domain コーディングエージェントの支援 contorollerのテスト概念
controller Controllerは外から中に入っていく世界であり、 UTとITでテスト実施する内容にかぶりがあることが多く感じる。 中の世界のテストが完了していたら、基本はcontroller層以下はMockにする必要なし。 (ブラックボックス、ホワイトボックス、ファットコントローラー、ファットサービスとか関係なく、 ITa(内部結合)相当のテストをやると品質・工数削減につながるのでは?
コーディングエージェントの支援 各テストレベルの整理 区分 目的 方針形式 検出欠陥 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 ビジネス価値/受入基準の最終確認 要件誤解 / 受入条件未達 / 運用性 ビジネスユーザ / プロダクトオーナ 原則実機 要件誤解 / 受入条件未 達 / 運用性
web service repository persistence external api batch domain コーディングエージェントの支援 各テストレベルの整理
controller 各モジュール:UT 赤枠:ITa 青枠:ITb 緑枠:Sta,Stb,UAT
コーディングエージェントの支援 ガイドラインの修正 ### controller test * 統合テスト(Integration Test)で実装してください - SpringBootTest
+ WebTestClientを使用してください - SpringBootTestの起動時間を少なくため、起動設定は共通化してください - IntegrationTestBaseを継承してWireMock設定を共通化してください(推奨) - WireMockサーバーの起動・停止が自動化される - 外部API URLが自動的にWireMockサーバーに設定される - 各テストはstubFor()によるレスポンス定義のみに集中できる - DBのデータはSQLで事前に投入してください - DB依存の可能性があるので@Transactionalを必ずつけてください * 実現不可能なテストはMokitoを利用してください
コーディングエージェントの支援 ガイドラインの修正 @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 -> { ... 比較していく処理
1. 明確な指示と結果を予測できる作業はかなり効率が良くなる a.単体テストとか当てはまりやすい気がする i.ガイドラインを細かく記載する。 1.明確な指示をする。 (一部とかやめる) 2.「お願い」だけでできるのは人間だけ 2.人間でわからないことは、AIがわかるわけがない。 a.知識があるので教えてもらえる
i.最適かどうかOK or NGの判断が困難 3.正しいことが正しいわけじゃない a.工数と品質のバランスを考えよう b.アンチパターンも選択することある AI コーディングエージェントの支援 コーディングエージェントをうまく使いこなす AI Playwrightとか使うと E2Eもある程度自動化できる