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

20年もののレガシーJavaプロダクトに自動テストを組み込んできた話

Avatar for げんげん げんげん
November 14, 2025
240

 20年もののレガシーJavaプロダクトに自動テストを組み込んできた話

JJUG CCC 2025 Fallの登壇資料です。

X
https://x.com/gengenmusic0719
lit.link
https://lit.link/gengen0719

Avatar for げんげん

げんげん

November 14, 2025
Tweet

Transcript

  1. 保守してきたプロダクトの紹介 とあるERPパッケージソフト 保守期間20年越え Javaソースコード 約380万⾏ 開発組織の⼈員は 約50名 Java 1.4時代のコードが残っている 当初からWebアプリとして作られているので、JSPの実装が残っていたり、

    ⾊んなフレームワークの実装が地層のように積み重なったりしている。 このプロダクトに⾃動テストの仕組みを整備してきた経験と 新しいプロダクトを作る際に⾃動テストを整備している経験をもとに話します。
  2. 本講演内での⾃動テストの分類の定義 引用: 開発生産性の観点から考える自動テスト( 2024/06版) https://speakerdeck.com/twada/automated-test-knowledge-from-sava nna-202406-findy-dev-prod-con-edition?slide=40 Small JVM単体でのJUnit Test 対象のクラスが1つか複数かは問わない

    Medium データベースに接続するJUnit Test Large E2Eテスト テストだけではなく検証まで広げると、 「ビルドの検証」や「静的解析」が Smallより⼩さい検証としてあり それらも運⽤も重要。
  3. JUnit TestをCIで⽇次実⾏する Small sizeのTestをCI上で実⾏することは全く難しくありません。 JavaでGradleを利⽤している場合であれば標準の test タスクを実⾏すれば良いです。 GitHub Actionsで毎⽇夜 9:30に

    テストを実⾏するのであればこんな感じです。 DistributionやJavaのバージョンは 実際に利⽤しているものと揃える、 ⾮公開の内製ライブラリに依存する場合は 認証や通信経路確保などが必要になりますが、 それも難しい話ではありません。
  4. Pre mergeでの実⾏ 少なくとも⽇次でテストが実⾏されて結果が通知されていれば、テストが腐ることはありません。 しかしより早く検証の失敗を検知できるように Pre mergeにもテストを組み込むことができないかを検討しましょう。 Pre mergeとはマージされる前、という意味で、 Pull RequestやMerge

    Requestが作成された際にテストを実⾏し、 失敗した場合はmainブランチにマージされるのを防ぐことができます。 これによって修正者他の開発者に影響する前に検証を⾏うことができます。 また修正者が間違いなく⾃分の修正によって失敗したことを把握できる点も利点です。
  5. 啓蒙活動 - テスト駆動開発の動画視聴会 TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング

    https://www.youtube.com/watch?v=Q-FJ3XmFlT8 - JUnit の基礎知識の勉強会 - レガシーコードにUnit Testを書くためのテクニックの勉強会 レガシーコード改善ガイドの内容を実際のプロダクトコードに適⽤ https://www.shoeisha.co.jp/book/detail/9784798116839 - 外部講師の⽅を招いた全社向けの講演 開発組織全体の盛り上がりの醸成と経営層への単体テストの重要性の訴求
  6. やったこと - ⽇次でテストを全件実⾏する仕組みを整備 - pre mergeで変更の⼊ったクラスのテスティングペアのテストを実⾏する仕組みを整備 - 開発者が開発環境でテストを実⾏できる仕組みを整備 そのために以下を⾏いました。 -

    データベースにアクセスするテストとしないテストの実⾏を分離 - テストクラス内でDataSource、Connectionを取得する仕組みを整備 - 開発者やCIで利⽤するデータベースのDocker Imageを整備 - テスト時のデータベースのデータの扱いのルールを整備 データベースを扱うことで⾊々な準備が必要になり、仕組みも⼤がかりに。 CIでのテスト実⾏時には全てのテストが同じデータベースを使うため、データ管理の戦略も必要。 プロダクトの固有の事情によって異なる部分が多いものだと思いますが、 できるだけ根拠を含みつつ話しますので、⼀事例として聞いていただければと思います。
  7. 仕組みの実装 - データベースに接続するテストコードはmain, test とは別のフォルダに置くようにする - 専⽤のアノテーション @DatabaseAccessTest を作成 データベースにアクセスしたいテストにはこれをつけるようにする。

    - gradle に databaseAccessTest というタスクを作成する - CIや開発者の端末で動かせるデータベースのDocker imageを準備(後述) - テスト⽤のConfigurationでlocalhostのデータベースへの接続を取得する仕組みを整備 (Spring未対応のコードが多いのでConnectionやDataSourceを取得するUtilityも⽤意)
  8. 仕組みの実装 これで開発者の端末のIDEでは - @Test がついたテストはいつでも実⾏できる - @DatabaseAccessTest がついたテストはテスト⽤のデータベースを起動したら実⾏できる CI上では -

    gradle test で @Test がついたテストが実⾏できる - gradle databaseAccessTest で @DatabaseAccessTest がついたテストが実⾏できる (CI上にデータベースを起動する必要がある) という状態になりました。 開発者ローカルで実⾏する際の使⽤感を変えず、かつ データベースに接続するかどうかで明確にCIでの実⾏を分離できるように、このような実装にしました。
  9. やったこと - ⽇次でE2E Testを実⾏する仕組みを整備 そのために以下を⾏いました。 - データ投⼊専⽤の環境を作成(ユーザーや打鍵評価環境相当のもの) - データ投⼊環境で投⼊したデータでアプリケーション全体が動作するDocker Imageの作成

    - Dockerで⽴ち上げたアプリケーションにE2E Testを実施する仕組みを作成 まだ実験段階で、⽴ち上げプロジェクトが継続中ですが、少しずつ対象の機能を広げています。 実装はCodeceptJS + PlaywrightでJavaの話ではないのでデータ管理に関する話だけ。
  10. 実装のポイント - ここでも⽇次以上で実⾏が⾏える仕組みにすることを最優先にした - アプリケーションを動作させるため、かなり多くのデータの準備が必要 - 忠実性の⾼いテストが⾏える仕組みなので、テストデータも忠実性の⾼いものにしたい 忠実性の⾼いテストデータにするためには アプリケーションの機能を使ってデータを投⼊する必要があった。 機能を使ってデータ投⼊する場合、

    事前に登録したデータをスナップショットにしてDockerで利⽤するか、 テストケースの中で全てのデータ投⼊をするかの⼆択だった。 しかし対象のプロダクトは別のプロダクトで登録したデータを元に動作する箇所が多く 全てのデータを機能で投⼊するには別のプロダクトも動作させる必要があった。 そのためテストケースとデータの関連性は⼀定諦めて、スナップショットにする⽅法にした。