Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
ユニットテスト実行を45% 高速化した Repository テスト戦略 JJUG CCC 2023 Spring 2023.06.04
Slide 2
Slide 2 text
自己紹介 ● 名前:Yuya Nishimaki/西牧 佑哉 ● 所属:BABY JOB株式会社 ○ エンジニア歴3年 ○ バックエンドエンジニア ○ Java, Spring Bootを使用した自社サービスの開発 ● 初登壇です何卒🙏 2
Slide 3
Slide 3 text
BABY JOB 3
Slide 4
Slide 4 text
4
Slide 5
Slide 5 text
5
Slide 6
Slide 6 text
6
Slide 7
Slide 7 text
アジェンダ 7
Slide 8
Slide 8 text
アジェンダ ● 前提となる技術や用語 ● ユニットテストが遅い ● ユニットテストが遅い原因 ● ユニットテストを高速化する ● 高速化の結果 ● 後日談 ● まとめ 8
Slide 9
Slide 9 text
前提となる技術や用語 9
Slide 10
Slide 10 text
言語やツール ● Java8 ● Spring Boot ● オニオンアーキテクチャ ● Spring Data JPA, Hibernate(ORM) ● JUnit5 ● H2(テスト用DB) ● GitHub Actions(CI) 10
Slide 11
Slide 11 text
オニオンアーキテクチャ 11 依存関係は外側から内側に向かう ● ドメイン層 ○ ビジネスルールやドメイン知識を表現する ○ リポジトリを定義する ● アプリケーション層 ○ ユースケースを実現する ○ アプリケーションサービスを定義する アプリケーション層 プレゼンテーション層 テスト インフラ層 ドメイン層
Slide 12
Slide 12 text
リポジトリ ● 永続化層に対する処理を抽象化したもの ● インタフェースをドメイン層に、実装をインフラ層に定義する ● Spring Data JPAを利用 12
Slide 13
Slide 13 text
ユニットテスト ● JUnitで行うテストのことをユニットテスト/テストと呼ぶこととする 13
Slide 14
Slide 14 text
モック ● テストダブル全般をモックと呼ぶこととする ○ スタブやスパイと区別しない ● モックライブラリによって生成したインスタンスという理解で🙆 14
Slide 15
Slide 15 text
本題 15
Slide 16
Slide 16 text
ユニットテストが遅い 16
Slide 17
Slide 17 text
ユニットテストが遅い ● ローカルで4分半、CIで5分半ほど ○ 特にアプリケーションサービスのテストが遅い ● (参考)テストメソッド数:690 17
Slide 18
Slide 18 text
ユニットテストが遅いことよる問題 ● 気軽にユニットテストを実行できない ● フィードバックを得るまでの時間が長くなり、バグの発見が遅れる ● さらにテストが遅くなることを懸念して、テストケースを追加しづらい ● CIが通るまでマージできず、待ち時間が発生する ● CIサービス(GitHub Actions)の実行時間(Quota)が枯渇する 18
Slide 19
Slide 19 text
ユニットテストが遅い原因 19
Slide 20
Slide 20 text
ユニットテストが遅い原因 ● コンピューティングリソースの問題 ● テストの実装方法がよくない 20
Slide 21
Slide 21 text
ユニットテストが遅い原因 ● コンピューティングリソースの問題 ● テストの実装方法がよくない 21
Slide 22
Slide 22 text
よくないポイント(1/2) ● アプリケーションサービスのテストでデータベースを使用している ○ データベースを使用すると・・・ ■ テストの実行時間が長くなる ■ デグレ防止の観点では優れている 22
Slide 23
Slide 23 text
よくないポイント(1/2) ● @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)による ロールバック ○ Springのアノテーション ○ テストケースごとにアプリケーションコンテキストを新しくする(重い処理) 23
Slide 24
Slide 24 text
よくないポイント(2/2) ● リポジトリを過剰にテストしている ○ アプリケーションサービスとリポジトリでテストが重複している 24
Slide 25
Slide 25 text
リポジトリを過剰にテストしている 25 アプリケーション サービスA リポジトリ DB アプリケーション サービスB アプリケーションサービスBの テストがカバーする範囲 リポジトリのテストが カバーする範囲 アプリケーションサービスAの テストがカバーする範囲
Slide 26
Slide 26 text
背景 ● 開発初期の頃・・・ ○ アプリケーションサービスをデータベースも含めてテストすることで品質を担保しようとした ○ 開発初期はテストケースも少ない ● 開発が進むにつれテストケースが増える・・・ 26
Slide 27
Slide 27 text
ユニットテストを高速化する 27
Slide 28
Slide 28 text
ユニットテストを遅くしていた原因まとめ ● アプリケーションサービスのテストでデータベースを使用している ○ @DirtiesContextによるロールバック ● リポジトリを過剰にテストしている 28
Slide 29
Slide 29 text
テストの責務を分離する ● リポジトリのテスト ○ なければ新設 ○ データベースとのCRUDをテスト ● アプリケーションサービスのテスト ○ リポジトリをモックにする ○ ユースケースをテスト 29
Slide 30
Slide 30 text
リポジトリを過剰にテストしている(再掲) 30 アプリケーション サービスA リポジトリ DB アプリケーション サービスB アプリケーションサービスBの テストがカバーする範囲 リポジトリのテストが カバーする範囲 アプリケーションサービスAの テストがカバーする範囲
Slide 31
Slide 31 text
テストの責務を分離する 31 アプリケーション サービスA リポジトリ DB アプリケーション サービスB アプリケーションサービスBの テストがカバーする範囲 リポジトリのテストが カバーする範囲 アプリケーションサービスAの テストがカバーする範囲 モックリポジトリ モックリポジトリ
Slide 32
Slide 32 text
高速化の結果 32
Slide 33
Slide 33 text
高速化の結果 ● @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)を使用したテスト ケース数 ○ 203 → 0👏 ● CIでの実行時間 ○ 約45%改善(338s → 183s)👏 ● ローカルでの実行時間 ○ 約75%改善(255s → 64s)👏 33
Slide 34
Slide 34 text
リポジトリをモックにすることのメリット ● 高速化できる ● アプリケーションサービスのテストを作成しやすくなる 34
Slide 35
Slide 35 text
リポジトリをモックにすることのデメリット ● 品質低下への不安 ○ レイヤーを跨ぐテストがなくなる ○ インフラ層まで含めた確認ができなくなる ● リポジトリのテストをどこまでやるか ○ JPAのテストになってしまわないか 35
Slide 36
Slide 36 text
品質低下への不安に対する考え方 ● 高速化によるメリットの方が大きいと判断 ○ リポジトリのテストも作成 ○ 既存のテストパターンを網羅する形でモック化する 36
Slide 37
Slide 37 text
リポジトリのテストをどこまでやるか ● JPQLで書いたものはテストする ● メソッド名から自動実装されるものはテストしない 37 こっちはテストする
Slide 38
Slide 38 text
後日談 38
Slide 39
Slide 39 text
後日談 ● GWに「単体テストの考え方/使い方」を読んだ ● モックを使うことは高速化の根本的な対策ではなかった ○ モックを使用すると実装に依存する ○ リファクタリングへの耐性が弱くなる 39
Slide 40
Slide 40 text
後日談 ● モックを使いたくなる理由(自分の解釈) ○ 高速にテストしたい ○ なぜか?テストケースが多いから ○ なぜか?ビジネスロジックに対してテストしているから ○ なぜか?アプリケーションサービス内にビジネスロジックがあるから 40
Slide 41
Slide 41 text
後日談 ● レイヤーの責務を守る ● 責務を守ると ○ ドメイン層にビジネスロジックがある ○ アプリケーションサービスのテストケースは少なくなる ● テストケース数が少なければデータベースを使用してテストを行える ● モックを使ったユニットテストの高速化は現実解 ○ アプリケーションサービス内に意図せずビジネスロジックを書いてしまう ○ すでに書かれている ○ アプリケーションサービスの数が多い 41
Slide 42
Slide 42 text
まとめ 42
Slide 43
Slide 43 text
まとめ ● @DirtiesContextを使うとユニットテストは遅くなる ● レイヤーによってテストの責務を分離しよう ● リポジトリをモックにすると高速化できる ● レイヤーの責務を守ろう ● モックを使った高速化は現実解 43
Slide 44
Slide 44 text
ご清聴ありがとうございました 44
Slide 45
Slide 45 text
Q&A Q. @Transactionalによるロールバックは検討しなかったのか? A. 検討はした。テスト対象自身がトランザクション管理を行う場合に、テスト側で開始し たトランザクションを使用してしまい、純粋なテストにならない。 45
Slide 46
Slide 46 text
Q&A Q. テストの並列実行はやらなかったのか? A. なぜか誰も気づかなかったのでやってない。今はやっている。 46
Slide 47
Slide 47 text
Q&A Q. 費用対効果をどのように判断したのか? A. 改修しない場合のテスト時間、改修コスト、改修による短縮時間を比較 47