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