[Ruby on Rails] テストのフィクスチャの動作に関するコードを読んでみた

![READYFORアドベントカレンダー2023カバー画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674911/82959657-8786-1d95-f6cf-32ab83d9b5d9.png)


## はじめに
READYFOR でエンジニアとして働いている masaki です。
この記事は READYFOR Advent Calendar 2023 の14日目の記事になります。（投稿遅れました…🙇）

他の記事もぜひご覧ください！

https://qiita.com/advent-calendar/2023/readyfor

[Rails テスティングガイド \- Railsガイド](https://railsguides.jp/testing.html#%E3%83%95%E3%82%A3%E3%82%AF%E3%82%B9%E3%83%81%E3%83%A3%E3%81%AE%E5%8B%95%E4%BD%9C) を読んでいると以下のような記載があります。

> 1. フィクスチャに対応するテーブルに含まれている既存のデータをすべて削除する
> 1. フィクスチャのデータをテーブルに読み込む
> 1. フィクスチャに直接アクセスしたい場合はフィクスチャのデータをメソッドにダンプする

説明を読んで、そうなのか〜と思いつつ、どんなコードでそうなっているのか（なぜか）興味が湧き、少し調べたことがあったのでその内容を共有させていただこうと思います。
大まかな処理の流れを追うだけで、細かい実装されている内容については飛ばしています。おかしい点があれば指摘していただけるとありがたいです。

※ちなみ READYFOR では [RSpec](https://rspec.info/) と [factory_bot](https://github.com/thoughtbot/factory_bot) を使って Rails のテストを書いているので、個人の興味本位で調べた内容になります

## 対象のバージョン等

調査した Rails のバージョンは `Rails 7.0.8` で、以下のコマンドを実行して作ったアプリ使っています。 (`Rails 7.1` ではないので、最新とは異なるかもしれないです...🙏)

```bash
> rails new blog
> rails generate scaffold Article title:string content:text
```

## fixtures の読み込み処理を探す

とりあえず関係ありそうな処理がないかエディタ（VSCode）の検索機能を使って、探してみます。`fixture` で検索をかけてみると `test_helper.rb` で `fixtures` というメソッドが呼び出されています。

```ruby
class ActiveSupport::TestCase
  # 略
  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
  fixtures :all
  # 略
end
```

コメントからもここでフィクスチャのセットアップが行われていることがわかります。メソッドの定義元は `ActiveRecord::TestFixtures` だったので、こちらのコードを見ていきます。

## ActiveRecord::TestFixtures を読む

対象のコードは以下のものです。

[rails/activerecord/lib/active\_record/test\_fixtures\.rb at v7\.0\.8 · rails/rails](https://github.com/rails/rails/blob/v7.0.8/activerecord/lib/active_record/test_fixtures.rb#L42)

### [#fixtures](https://github.com/rails/rails/blob/fc734f28e65ef8829a1a939ee6702c1f349a1d5a/activerecord/lib/active_record/test_fixtures.rb#L42) メソッド

処理の内容としては次の2つのことをやっています。

1. 引数をもとに、テストで読み込むフィクスチャ名を `class_attribute :fixture_table_names` に設定する
2. フィクスチャのデータにアクセスするためのプライベートメソッドを作成する

`test_helper.rb` のコメントにあったように、引数が `:all` の場合は `test/fixtures/*.yml` に該当する全てのファイル名が `fixture_table_names` が設定されます。
その後、[#setup_fixture_accessors](https://github.com/rails/rails/blob/fc734f28e65ef8829a1a939ee6702c1f349a1d5a/activerecord/lib/active_record/test_fixtures.rb#L56) メソッドが呼び出され、読み込んだフィクスチャに直接アクセスするためのプライベートメソッドが作成されています。モジュールと [\#define\_method](https://docs.ruby-lang.org/ja/latest/method/Module/i/define_method.html) を活用して、動的にメソッドを include しているようです。

各テストクラスは `fixtures` メソッドを呼び出している `ActiveSupport::TestCase` を継承しているため、設定が全テストで適応されます。

```bash
# デバックでの確認
❯ rails test
# (省略)
     3| require 'test_helper'
     4|
     5| class ArticlesControllerTest < ActionDispatch::IntegrationTest
     6|   setup do
=>   7|     binding.break
     8|     @article = articles(:one)
     9|   end
# (省略)
(rdbg) ArticlesControllerTest.ancestors.include? ActiveSupport::TestCase
true
(ruby) fixture_table_names
["articles"]
(ruby) ArticlesControllerTest.private_instance_methods.grep /articles/
[:articles]
```

これで読み込み対象のフィクスチャの設定と、フィクスチャのデータにアクセスするためのメソッドの作成の流れは分かりました。後は、テスト実行前のデータの読み込み処理を見てみます。

### [#setup_fixtures](https://github.com/rails/rails/blob/fc734f28e65ef8829a1a939ee6702c1f349a1d5a/activerecord/lib/active_record/test_fixtures.rb#L108) メソッド

各テストの実行前（`#setup` 前）の [#before_setup](https://github.com/rails/rails/blob/fc734f28e65ef8829a1a939ee6702c1f349a1d5a/activerecord/lib/active_record/test_fixtures.rb#L9) で、`#setup_fixtures` が実行されるようになっており、ここでフィクスチャのデータの読み込み処理が行われています。テーブルへのデータの投入処理は、[#load_fixtures](https://github.com/rails/rails/blob/fc734f28e65ef8829a1a939ee6702c1f349a1d5a/activerecord/lib/active_record/test_fixtures.rb#L274) メソッドを呼び出して実行しており、ここで `fixture_table_names` が参照されています。このデータの投入処理は初回のみ実行され、それ以降は初回に読み込んだフィクスチャのデータを利用しています。（テストの設定にもよると思います。）

### [#teardown_fixtures](https://github.com/rails/rails/blob/fc734f28e65ef8829a1a939ee6702c1f349a1d5a/activerecord/lib/active_record/test_fixtures.rb#L172) メソッド

各テストの実行後（#teardown 後）は、[#after_teardown](https://github.com/rails/rails/blob/fc734f28e65ef8829a1a939ee6702c1f349a1d5a/activerecord/lib/active_record/test_fixtures.rb#L14) 内の `#teardown_fixtures` によって、トランザクションをロールバックしてテストでのデータの変更をリセットしています。

トランザクションについては以下の記事などが参考になるかもしれないです。

- [トランザクションとはなにか？どんな時に使うか？ Railsでの使用方法 \- Qiita](https://qiita.com/tomokiyao/items/8697b523a0a5b9cf0bec)
- [特定のSpecでだけトランザクションのロールバックを無効にする \- Qiita](https://qiita.com/takeyuweb/items/e7261e9274b3b31d933c)

## 最後に

フィクスチャの動作の流れを大まかに見ていったので、まだまだ理解できていない箇所はたくさんありますが、gem のコードを読むと今まで見たことがないコードを学ぶ機会となって、とても勉強になるなと思いました。


読んでくださった方、ありがとうございました🙏