RSpecテスト高速化の事例を学ぶ！Evil Martians社の5倍高速化テクニックを実践


::: note info
この記事は[リンクアンドモチベーション AdventCalendar2024](https://qiita.com/advent-calendar/2024/lmi)の 14日目の記事です
::: 

# この記事でお伝えしたいこと

Evil Martians社が発信した記事で、RSpecテストの5倍高速化を実現した事例を紹介するものがありました。
その事例から、テスト最適化に役立つ3つのマインドセットと、3つの具体的手法を学びつつ、ここの記事にて紹介させていただきます！

```
「3つのマインドセット」
1. 価値のある最適化対象を見極める
2. 簡単な改善を優先する
3. タイミングの測定と比較

「3つの手法」
1. コールバックの無効化
2. ファクトリの最適化
3. 特定のテストファイルの最適化
```
また、これらを参考にして実践した結果もまとめています。
「rspecを高速化したい！」「テストを少しでも軽くしたい！」という方のお役に立てれば幸いです！


# ご挨拶
こんにちは！ リンクアンドモチベーションでバックエンドエンジニアをしております中島と申します！今回は、**Ruby Weeklyのインプット × 実務でのTestProfの経験**を掛け合わせて記事を作成しました。

Ruby Weeklyのインプット：
Evil Martians社の記事に書かれていた
テストを5倍高速化するツールとテクニックの事例、その根底になる考え方と具体的手法

実務でのTestProfの経験：
TestProfによる診断と、高速化事例の具体的手法を実践して、テストがどれくらい改善できたか

- RSpecが重い。どうにかしたい
- テスト最適化に必要な3つのマインドセットを知りたい
- テスト高速化に向けた、具体的なHowの事例を知りたい

ぜひ、読んでいただけると幸いでです！


# Ruby Weeklyについて
ざっくり説明しますと、**Rubyに関するニュース（ベストプラクティスや人気のGemなど）や最新情報をまとめた週報**です。

イメージはこんな感じです↓

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621716/fd51d35d-ddf9-9e0c-9149-ca32c2227ddc.png" alt="サンプル画像" width="500" height="300">


メールアドレスの登録だけで見ることができるので、気になった方は見てみてください！
https://rubyweekly.com/

# TestProfについて
こちらもざっくり説明しますと、**RSpecを全体/クエリ/factoryなどの粒度で各実行数と時間を測定できるGem**です。
https://github.com/test-prof/test-prof


以下は実際に自チームで実行したものです。トータルでかかる時間が出力されています。
(上が 2024/9/18、下が 2024/10/24 に実施した結果です)

<img alt="サンプル画像" width="400" height="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621716/590c71f9-a072-675f-20bb-a70b79c29d68.png">


<img alt="サンプル画像" width="400" height="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621716/e0d07434-91d9-9bdb-9262-de61399550d4.png">


他にも、時間がかかっている部分をランキングで出してくれるので、時間がかかっているところをピンポイントで炙り出せます。


それでは、ここから記事をご紹介します！

# テストを5倍高速化するツールとテクニックの事例
今回取り上げる記事はこちらです！

https://evilmartians.com/chronicles/railing-against-time-tools-and-techniques-that-got-us-5x-faster-results

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621716/ec233436-0d4f-e314-8b88-8ccd9c6f7f63.png)

和訳すると、以下のようになります。
````
和訳) 時間に逆らう: テストを5倍高速化するツールとテクニック
— Evil Martiansが最近、クライアントのCIパイプラインを最適化し、
コードのテストと変更を高速化して、最終的に5倍高速な結果を得るのを支援した方法を紹介します。
StackprofとTestProfがそれを解決します。
````
ここからはEvil Martians社の記事に書かれていました
最適化に必要な **「3つのマインドセット」** と **「3つの手法」** をご紹介します！
主にTestProfの活用例です。これにより、テストボトルネックの診断と修正が可能になることが示されています。

このやり方をプロダクトに用いれば、5倍まではいかなくても、今より高速化できるのは間違いなしです！まずはマインドセットからお伝えします。

## 最適化に必要な「3つのマインドセット」
### 1. 価値のある最適化対象を見極める

このマインドセットは「最適化する対象の優先順位付け」に焦点を当てています。
例えば、userファクトリのように、ほぼすべてのテストで使用される頻出ファクトリを最適化することで、全体の実行時間に大きな影響を与えられるというものです。逆に、テスト頻度が低く、変更の少ないファイルなどに時間をかけるのは非効率と言えます。

### 2. 簡単な改善を優先する 

優先順位を設定する際に、複雑さよりも「時間対効果」を重視することが大切です。
例えば、1秒の改善のために4時間を費やすのは非効率であり、あまり効果が見込めない微細な改善よりも、簡単かつ効果が大きい改善を探すべきです。

### 3. タイミングの測定と比較
最適化の効果を確かめるために、改善前後の実行時間を測定し、数値で比較することが重要です。予期せぬ実行時間の増加などが発生しないよう、測定と比較を繰り返してチェックしながら最適化を進めることで、効果的な改善が可能になるとされています。

-----


当たり前のように聞こえる部分もありますが、
この観点が頭に入っていれば、明確な意図を持って、漏れなく効率化できそうですね。

「じゃあ、そのマインドセットをもって具体的には何をしていくんだ？」というところもご紹介します！

## 最適化に必要な3つの手法

### 1: コールバックの無効化
History#auto_add_historyなどのActive Recordコールバックがテストで頻繁に呼ばれていたにも関わらず、実際には必要なのは約1%のテストのみでした。
そこで、テストでコールバックを無効にするため、Testingモジュールを追加し、RSpecのタグ付け機能を使用して必要な場合にのみコールバックを有効にしました。
結果的に、テスト時間は53分から33分に短縮されました。

### 2: ファクトリの最適化
次に、FactoryProfレポートで最も時間のかかるファクトリに焦点を当てました。
これらのファクトリは、デフォルトで多くの関連オブジェクトを生成していましたが、実際には不要な場合も多く存在していることが判明しました。
必要な場合にのみ関連付けするように変更し、さらにTestProfのAnyFixtureツールを使用して、テスト全体で再利用できるデフォルトユーザーを作成しました。
結果的に、テスト時間は33分から21分に短縮されました。

### 3: 特定のテストファイルの最適化
最後に、let_it_beを使用しました。テストごとに同じデータを再生成するのではなく、ファイル全体で一度生成することを実現しました。
結果的に、テスト時間は最終的に21分から11分まで短縮されました。

------------------------------------------------------
このような流れで、Evil Martians社の記事では、53分から11分への短縮が実現されています。

```
「3つのマインドセット」
1. 価値のある最適化対象を見極める
   => 多くのテストで使用されるuserファクトリなどの最適化は大きな影響があり、価値が高い。 
2. 簡単な改善を優先する
   => 時間をかけて微細な改善を追求するより、簡単で効果が大きい箇所を優先する。
3. タイミングの測定と比較
   => 最適化前後の実行時間を比較して、効果を確認する。

「3つの手法」
1. コールバックの無効化
   => RSpecのタグ付け機能を使用して必要な場合にのみコールバックを有効にする。
2. ファクトリの最適化
   => 最も時間のかかるファクトリにて、関連付けを必要な場合のみ生成するように変更する。
      AnyFixtureツールを使用して、テスト全体で再利用できるデフォルトユーザーを作成する。
3. 特定のテストファイルの最適化
   => let_it_beを使用してデータをファイル全体で一度生成するように変更する。
```


#### AnyFixtureツールとlet_it_beの違い
記事を読んでいて、それぞれの違いが気になったので、まとめました。

**let_it_be** は、個別のテストファイルで使い捨てデータを生成するのに適しています。
スコープが限定されているので、**意図しない影響を避けやすい**という利点があります。

**AnyFixture** は、テストスイート全体で共通データを効率的に再利用でき、データ生成コストの削減に加えて、**広範囲で一貫性のある環境を提供できます**。
ただし、依存関係が強くなることで、**テスト間でのデータ衝突**が発生するリスクもあります。

 用途に応じて使い分けるのが良さそうです！

| 機能          | let_it_be                       | AnyFixture                       |
|---------------|---------------------------------|----------------------------------|
| **生成タイミング** | テストファイル内で一度だけ生成         | テストスイート全体で一度生成         |
| **利用スコープ** | テストファイルまたは特定のコンテキスト内で使用 | テストスイート全体で使用         |
| **用途**       | 特定のテストファイルやコンテキスト内での再利用 | 全テストで共通するデータの再利用   




# 高速化の記事を読んで、いざ実践！

この記事を読み、チームに展開した結果、改善できるところは積極的に修正していこうという話になりました。

### 私たちのチームでの実践
Evil Martians社の事例を参考にし、まず「特定のテストファイルの最適化」に着手しました。
具体的には**let_it_be**を用いて無駄なデータ生成を削減しました！

#### 結果

上が 2024/11/20、下が 2024/12/12 に実施した結果です。
examplesは50ケース分増えているにも関わらず、合計実行時間が19秒削減できました！

<img alt="サンプル画像" width="1000" height="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621716/e42dd0a7-8921-7a14-153a-f0a97d4c1862.png >

<img alt="サンプル画像" width="1000" height="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621716/1affb08e-a363-7799-42af-a3f73dab72ba.png >


#### 分析
let_it_beを使ったことにより、無駄なletやbeforeが少なくなり、時間が削減されているのがわかります！↓

<img alt="サンプル画像" width="400" height="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621716/c818ae14-8aa0-9596-eb0d-397226f26ed4.png>

<img alt="サンプル画像" width="400" height="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621716/259cca0c-3c91-0fdc-fc1b-f303e94a4f6a.png>

その他にもrequestやmodelに関する時間が減らせていることもわかっています！↓

<img alt="サンプル画像" width="400" height="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621716/f8c429b6-bf1c-848f-317b-6131bf7f2181.png>

<img alt="サンプル画像" width="400" height="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621716/e601d5a4-8f2b-0a58-6c29-8799b8d663f2.png>


## 今後の展望
TestProfで診断したことによって、before(:each) が長いスイートや、大量のFactoryBot生成箇所が明確になっています。
今後は、これらを対象に、「コールバックの無効化」や「ファクトリの最適化」にも取り組んでいきます！

皆さんも、テストの最適化に必要な「3つのマインドセット」と「3つの手法」をぜひ試してみてください！
ここまで読んでいただき、ありがとうございました！

### 余談
TestProfを紹介する際に、2024/9/18 と 2024/10/24 に自チームで実施した例は、1ヶ月で2分強も時間が増えてしまっています。気になって再計測したところ、所要時間が変わりました。大きく結果がブレた場合など、**必要に応じて複数回計測した方が良さそうです！**