【初心者向け】Railsでテストコードを書くときにテストデータにidをベタ書きするのはやめよう

## はじめに：idはベタ書きするな！

テストコードを書くのに慣れていない初心者の方のコードレビューをしていると、次のような方法でテストデータを作っているのをよく見かけます。

```ruby
# これはNGなコードです！！
alice = FactoryBot.create(:user, id: 1)
alice_report = FactoryBot.create(:report, user_id: 1)

bob = FactoryBot.create(:user, id: 2)
bob_report = FactoryBot.create(:report, user_id: 2)

# 以下、テストコードが続く
```

上のコードは何がまずいかわかるでしょうか？それは`id: 1`や`user_id: 1`のように、直接idをベタ書きしている点です。

## なんでidをベタ書きしちゃダメなの？

初心者さんの気持ちをエスパーすると、idをベタ書きするのはモデル同士のデータの関連を指定したいからだと思います。
たとえば、上のコードではaliceというユーザーとaliceが書いた日報（report）を関連付けるために、`id: 1`と`user_id: 1`を指定しています。

しかし、idは基本的にデータベースが自動的に割り当てるものです。アプリケーション側のコードを書いているときに、idをベタ書きするようなことはおそらくないでしょう。それと同じで、テストコード側でもidをベタ書きする必要はない、というか、してはいけません。

idをベタ書きする問題点は何でしょうか？
それはデータベースが自動的に採番したidと衝突する可能性が出てくることです。
この衝突を避けようとすると、テストコード上のすべてのidを開発者が自分で指定しなければならなくなります。こうなるとテストコードが大きくなってきたときに、

「このデータのidは何番だっけ？」

とか、

「新しくidを追加したいけど、まだ使ってないidは何番だっけ？」

というように、人力でidを管理するのが大変になってきます。

## 解決策：Railsらしい方法でデータの関連を指定しよう
というわけで、idをベタ書きせずにデータの関連を指定する方法をマスターしましょう。
たとえば、最初に紹介したテストコードは次のように書けば、idをベタ書きせずに済みます。

```ruby
alice = FactoryBot.create(:user)
# user_idではなく、モデルを引数にする
alice_report = FactoryBot.create(:report, user: alice)

bob = FactoryBot.create(:user)
# 同上
bob_report = FactoryBot.create(:report, user: bob)

# 以下、テストコードが続く
```

ポイントは`user: alice`や`user: bob`といった形で、idではなくモデルオブジェクトをそのまま引数として渡す点です。

こうすれば、Railsが自動的に`alice`や`bob`のidを日報モデルの`user_id`に指定してくれるので、テストコードを書く我々は`alice`や`bob`のidが具体的に何なのかを気にしなくて済むようになります。

## fixturesを使う場合
FactoryBotではなく、fixturesを使うときも考え方は同じです。以下のように、fixtures内でidをベタ書きするのはNGです。

```yaml:users.yml
# idをベタ書きするのはNG！！
alice:
  id: 1
  email: alice@example.com
  name: ありす
  encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %>

bob:
  id: 2
  email: bob@example.com
  name: ぼぶ
  encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %>
```

```yaml:reports.yml
# idをベタ書きするのはNG！！
alice:
  user_id: 1
  title: 初めての日報
  content: みなさんよろしくお願いします。

bob:
  user_id: 2
  title: 初めまして
  content: がんばります。
```

fixturesも次のようにすれば、idをベタ書きせずにモデル同士の関連を指定することが可能です。


```yaml:users.yml
alice:
  email: alice@example.com
  name: ありす
  encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %>

bob:
  email: bob@example.com
  name: ぼぶ
  encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %>
```

```yaml:reports.yml
alice:
  user: alice
  title: 初めての日報
  content: みなさんよろしくお願いします。

bob:
  user: bob
  title: 初めまして
  content: がんばります。
```

ポイントは`reports.yml`で`user: alice`のようにユーザーを指定している点です。
こうすれば`users.yml`で定義したユーザー`alice`と日報が関連付けされます。

## 素のActiveRecordを使ってテストデータを作成する場合
FactoryBotやfxituresを使わずに素のActiveRecordを使ってテストデータを作成することもできます。以下はidをベタ書きせずにActiveRecordを使ってテストデータを作成するコード例です。

```ruby
alice = User.create!(
  email: 'alice@example.com', name: 'ありす', password: 'password')
alice_report = alice.reports.create!(
  title: '初めての日報', content: 'みなさんよろしくお願いします。')

bob = User.create!(
  email: 'bob@example.com', name: 'ぼぶ', password: 'password')
bob_report = bob.reports.create!(
  title: '初めまして', content: 'がんばります。')

# 以下、テストコードが続く
```

ちなみに、Rails初心者さんのコードを見ていると、たまに次のような形でユーザーに関連する日報データを作成するコードを見かけます。

```ruby
alice = User.create!(
  email: 'alice@example.com', name: 'ありす', password: 'password')
# 以下のコードはちょっといまいち
alice_report = Report.create!(
  user_id: alice.id, title: '初めての日報', content: 'みなさんよろしくお願いします。')
```

`Report.create!(user_id: alice.id, ...)`のようなコードよりも、`alice.reports.create!(...)`のように書いた方が、短くてコードの意図が明確になる（`alice.reports.create` = 「アリスの日報を作る」と読める）のでベターです。

```ruby
# BAD
alice_report = Report.create!(user_id: alice.id, ...)

# GOOD!
alice_report = alice.reports.create!(...)
```

### createではなくcreate!を使う理由

なお、上のコード例ではどれも`create`ではなく`create!`を使っています。
これはバリデーションエラーに引っかかって保存に失敗したときに気づけるようにするためです。

```ruby 
# BAD （保存に失敗したことに気づけない可能性がある）
alice_report = alice.reports.create(...)

# GOOD （保存に失敗すると例外が起きてテストが落ちるので確実に気づける）
alice_report = alice.reports.create!(...)
```

## まとめ
というわけで、この記事ではRailsでテストコードを書くときにテストデータにidをベタ書きするのはやめよう、という話を書いてみました。

初心者さんのテストコードをレビューすると、かなりの確率でidをベタ書きしているコードに出くわします。
「ぎくっ、私もidをベタ書きしてた！」という人はこの記事を参考にして、ベタ書きのidに頼らずRailsらしい方法でモデル同士の関連を定義できるようになりましょう。

## 参考資料

https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md

https://railsguides.jp/testing.html

## PR：Railsでテストコードが書けるようになりたいという人へ
電子書籍「Everyday Rails - RSpecによるRailsテスト入門」では、Railsでテストコードを書いたことがないという人に向けて、テストコードの書き方を優しく詳しく説明しています。
RSpecの使い方だけでなく、FactoryBotの使い方も載っています。

まだ読んだことのない方はぜひ一度チェックしてみてください！

https://leanpub.com/everydayrailsrspec-jp/