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

ユニットテスト実行を 45% 高速化した Repository テスト戦略 / Repository Test Strategy Speeds up Unit Test

yukana
June 04, 2023

ユニットテスト実行を 45% 高速化した Repository テスト戦略 / Repository Test Strategy Speeds up Unit Test

私が開発を担当するプロダクトでは、ユニットテストの実行に時間がかかるという課題がありました。
その原因と改善の鍵は、データアクセス抽象化レイヤーとしての Repository クラスのテスト戦略にありました。

Spring Boot + Hibernate、オニオンアーキテクチャーで構築されたアプリケーションにおける、データベースアクセスを伴うユニットテストの課題と改善手法について、実アプリケーション開発でのユニットテストに対するトレードオフの判断を交えて説明します。

yukana

June 04, 2023
Tweet

Other Decks in Programming

Transcript

  1. ユニットテスト実行を45% 

    高速化した Repository テスト戦略

    JJUG CCC 2023 Spring 2023.06.04


    View Slide

  2. 自己紹介

    ● 名前:Yuya Nishimaki/西牧 佑哉

    ● 所属:BABY JOB株式会社

    ○ エンジニア歴3年

    ○ バックエンドエンジニア

    ○ Java, Spring Bootを使用した自社サービスの開発

    ● 初登壇です何卒🙏

    2


    View Slide

  3. BABY JOB

    3


    View Slide

  4. 4


    View Slide

  5. 5


    View Slide

  6. 6


    View Slide

  7. アジェンダ

    7


    View Slide

  8. アジェンダ

    ● 前提となる技術や用語

    ● ユニットテストが遅い

    ● ユニットテストが遅い原因

    ● ユニットテストを高速化する

    ● 高速化の結果

    ● 後日談

    ● まとめ

    8


    View Slide

  9. 前提となる技術や用語

    9


    View Slide

  10. 言語やツール

    ● Java8

    ● Spring Boot

    ● オニオンアーキテクチャ

    ● Spring Data JPA, Hibernate(ORM)

    ● JUnit5

    ● H2(テスト用DB)

    ● GitHub Actions(CI)

    10


    View Slide

  11. オニオンアーキテクチャ

    11

    依存関係は外側から内側に向かう


    ● ドメイン層

    ○ ビジネスルールやドメイン知識を表現する 

    ○ リポジトリを定義する 

    ● アプリケーション層

    ○ ユースケースを実現する 

    ○ アプリケーションサービスを定義する 

    アプリケーション層

    プレゼンテーション層

    テスト
 インフラ層

    ドメイン層


    View Slide

  12. リポジトリ

    ● 永続化層に対する処理を抽象化したもの

    ● インタフェースをドメイン層に、実装をインフラ層に定義する

    ● Spring Data JPAを利用

    12


    View Slide

  13. ユニットテスト

    ● JUnitで行うテストのことをユニットテスト/テストと呼ぶこととする

    13


    View Slide

  14. モック

    ● テストダブル全般をモックと呼ぶこととする

    ○ スタブやスパイと区別しない

    ● モックライブラリによって生成したインスタンスという理解で🙆

    14


    View Slide

  15. 本題

    15


    View Slide

  16. ユニットテストが遅い

    16


    View Slide

  17. ユニットテストが遅い

    ● ローカルで4分半、CIで5分半ほど

    ○ 特にアプリケーションサービスのテストが遅い

    ● (参考)テストメソッド数:690

    17


    View Slide

  18. ユニットテストが遅いことよる問題

    ● 気軽にユニットテストを実行できない

    ● フィードバックを得るまでの時間が長くなり、バグの発見が遅れる

    ● さらにテストが遅くなることを懸念して、テストケースを追加しづらい

    ● CIが通るまでマージできず、待ち時間が発生する

    ● CIサービス(GitHub Actions)の実行時間(Quota)が枯渇する

    18


    View Slide

  19. ユニットテストが遅い原因

    19


    View Slide

  20. ユニットテストが遅い原因

    ● コンピューティングリソースの問題

    ● テストの実装方法がよくない

    20


    View Slide

  21. ユニットテストが遅い原因

    ● コンピューティングリソースの問題

    ● テストの実装方法がよくない

    21


    View Slide

  22. よくないポイント(1/2)

    ● アプリケーションサービスのテストでデータベースを使用している

    ○ データベースを使用すると・・・

    ■ テストの実行時間が長くなる

    ■ デグレ防止の観点では優れている

    22


    View Slide

  23. よくないポイント(1/2)

    ● @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)による
    ロールバック

    ○ Springのアノテーション

    ○ テストケースごとにアプリケーションコンテキストを新しくする(重い処理)

    23


    View Slide

  24. よくないポイント(2/2)

    ● リポジトリを過剰にテストしている

    ○ アプリケーションサービスとリポジトリでテストが重複している

    24


    View Slide

  25. リポジトリを過剰にテストしている

    25

    アプリケーション

    サービスA

    リポジトリ

    DB

    アプリケーション

    サービスB

    アプリケーションサービスBの

    テストがカバーする範囲

    リポジトリのテストが

    カバーする範囲

    アプリケーションサービスAの

    テストがカバーする範囲


    View Slide

  26. 背景

    ● 開発初期の頃・・・

    ○ アプリケーションサービスをデータベースも含めてテストすることで品質を担保しようとした

    ○ 開発初期はテストケースも少ない

    ● 開発が進むにつれテストケースが増える・・・

    26


    View Slide

  27. ユニットテストを高速化する

    27


    View Slide

  28. ユニットテストを遅くしていた原因まとめ

    ● アプリケーションサービスのテストでデータベースを使用している

    ○ @DirtiesContextによるロールバック

    ● リポジトリを過剰にテストしている

    28


    View Slide

  29. テストの責務を分離する

    ● リポジトリのテスト

    ○ なければ新設

    ○ データベースとのCRUDをテスト

    ● アプリケーションサービスのテスト

    ○ リポジトリをモックにする

    ○ ユースケースをテスト

    29


    View Slide

  30. リポジトリを過剰にテストしている(再掲)

    30

    アプリケーション

    サービスA

    リポジトリ

    DB

    アプリケーション

    サービスB

    アプリケーションサービスBの

    テストがカバーする範囲

    リポジトリのテストが

    カバーする範囲

    アプリケーションサービスAの

    テストがカバーする範囲


    View Slide

  31. テストの責務を分離する

    31

    アプリケーション

    サービスA

    リポジトリ

    DB

    アプリケーション

    サービスB

    アプリケーションサービスBの

    テストがカバーする範囲

    リポジトリのテストが

    カバーする範囲

    アプリケーションサービスAの

    テストがカバーする範囲

    モックリポジトリ
 モックリポジトリ


    View Slide

  32. 高速化の結果

    32


    View Slide

  33. 高速化の結果

    ● @DirtiesContext(classMode =
    DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)を使用したテスト
    ケース数

    ○ 203 → 0👏

    ● CIでの実行時間

    ○ 約45%改善(338s → 183s)👏

    ● ローカルでの実行時間

    ○ 約75%改善(255s → 64s)👏

    33


    View Slide

  34. リポジトリをモックにすることのメリット

    ● 高速化できる

    ● アプリケーションサービスのテストを作成しやすくなる

    34


    View Slide

  35. リポジトリをモックにすることのデメリット

    ● 品質低下への不安

    ○ レイヤーを跨ぐテストがなくなる

    ○ インフラ層まで含めた確認ができなくなる

    ● リポジトリのテストをどこまでやるか

    ○ JPAのテストになってしまわないか

    35


    View Slide

  36. 品質低下への不安に対する考え方

    ● 高速化によるメリットの方が大きいと判断

    ○ リポジトリのテストも作成

    ○ 既存のテストパターンを網羅する形でモック化する

    36


    View Slide

  37. リポジトリのテストをどこまでやるか

    ● JPQLで書いたものはテストする

    ● メソッド名から自動実装されるものはテストしない

    37

    こっちはテストする


    View Slide

  38. 後日談

    38


    View Slide

  39. 後日談

    ● GWに「単体テストの考え方/使い方」を読んだ

    ● モックを使うことは高速化の根本的な対策ではなかった

    ○ モックを使用すると実装に依存する

    ○ リファクタリングへの耐性が弱くなる

    39


    View Slide

  40. 後日談

    ● モックを使いたくなる理由(自分の解釈)

    ○ 高速にテストしたい

    ○ なぜか?テストケースが多いから

    ○ なぜか?ビジネスロジックに対してテストしているから

    ○ なぜか?アプリケーションサービス内にビジネスロジックがあるから

    40


    View Slide

  41. 後日談

    ● レイヤーの責務を守る

    ● 責務を守ると

    ○ ドメイン層にビジネスロジックがある

    ○ アプリケーションサービスのテストケースは少なくなる

    ● テストケース数が少なければデータベースを使用してテストを行える

    ● モックを使ったユニットテストの高速化は現実解

    ○ アプリケーションサービス内に意図せずビジネスロジックを書いてしまう

    ○ すでに書かれている

    ○ アプリケーションサービスの数が多い

    41


    View Slide

  42. まとめ

    42


    View Slide

  43. まとめ

    ● @DirtiesContextを使うとユニットテストは遅くなる

    ● レイヤーによってテストの責務を分離しよう

    ● リポジトリをモックにすると高速化できる

    ● レイヤーの責務を守ろう

    ● モックを使った高速化は現実解

    43


    View Slide

  44. ご清聴ありがとうございました

    44


    View Slide

  45. Q&A

    Q. @Transactionalによるロールバックは検討しなかったのか?

    A. 検討はした。テスト対象自身がトランザクション管理を行う場合に、テスト側で開始し
    たトランザクションを使用してしまい、純粋なテストにならない。

    45


    View Slide

  46. Q&A

    Q. テストの並列実行はやらなかったのか?

    A. なぜか誰も気づかなかったのでやってない。今はやっている。

    46


    View Slide

  47. Q&A

    Q. 費用対効果をどのように判断したのか?

    A. 改修しない場合のテスト時間、改修コスト、改修による短縮時間を比較

    47


    View Slide