RSpecべからず集（DSLの構築が不適切な事例あれこれ）

## はじめに
本記事は僕がRSpecのコードレビューしていて、「ん？いやいや、こんな書き方しちゃダメだよ！」と思ったコード例をまとめたものです。

この記事でフォーカスするのはRSpecの[DSL](https://e-words.jp/w/DSL.html)を適切に構築してないケースです。

ネタが増えたらまた追記するので、気になる方はこの記事のストックをお願いします！（記事の更新時に通知欄でお知らせします）

それでは以下が本編です👇

## トップレベルのdescribeを2つ以上作らない

```ruby 
# NG
describe 'Foo spec' do
  # ...
end
describe 'Bar spec' do
  # ...
end
```

テストコードを読む際に、読み手は「当然ファイル全体が大きなひとつのdescribeブロックになっているはず」と信じ込んでいるので、予想に反するネスト構造になっていると脳内の予想と実際の実行結果が異なって混乱します。

以下のように大きなdescribeブロックで囲んで1ファイルにつきトップレベルのdescribeが1つだけになるようにしましょう。

```ruby 
# OK
describe 'My spec' do
  describe 'Foo spec' do
    # ...
  end
  describe 'Bar spec' do
    # ...
  end
end
```

もしくはFoo specとBar specを別々のファイルに分けるのも一手です。


```ruby 
describe 'Foo spec' do
  # ...
end
```

```ruby 
# Bar specは別ファイルにする
describe 'Bar spec' do
  # ...
end
```

## トップレベルにメソッドを定義しない

```ruby 
# NG
def foo
  # ...
end

describe 'Foo spec' do
  # ...
end
```

トップレベルにメソッドを定義するとプロジェクトのどこからでもアクセスできてしまいます。
スコープが広いメソッドはどこでどんなトラブルを引き起こすかわからないため、スコープは常に最小限に保つようにしましょう。

```ruby
# OK
describe 'Foo spec' do
  def foo
    # ...
  end

  # ...
end
```

## トップレベルにshared_examplesを定義しない

```ruby 
# NG
shared_examples 'expect something' do
  # ...
end

describe 'Foo spec' do
  it_behaves_like 'expect something'
  # ...
end
```

トップレベルにshared_examplesを定義するとプロジェクトのどこからでもアクセスできてしまいます。
別の場所で同名のshared_examplesが定義されていたりすると、予期しないタイミングでオーバーライドされる恐れがあります。

shared_examplesはdescribeの内部で定義するようにしてください。

```ruby 
# OK
describe 'Foo spec' do
  shared_examples 'expect something' do
    # ...
  end
  
  it_behaves_like 'expect something'

  # ...
end
```

## RSpecのDSLを動的に構築しない

```ruby 
# NG
describe 'Foo spec' do
  1.upto(10) do |n|
    let!(:"user_#{n}") { FactoryBot.create(:user, email: "user-#{n}@example.com") } 
  end

  # ...
end
```

RSpecのDSLはテストコードを読む人にとっての共通言語です。
これをループやら条件分岐やらで動的に操作すると、コードの読み手が「DSLが構築される過程」を脳内で再生しなければならなくなり、脳への負担が増えます。

あくまでRSpecのDSLは静的に構築するようにしてください。

```ruby 
# OK
describe 'Foo spec' do
  let!(:users) { FactoryBot.create_list(:user, 10) } 

  # ...
end
```

なお、上のコード例ではsequenceを使ってemailを生成するようになっている前提です。

```ruby 
FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "user-#{n}@example.com" }
    # ...
  end
end
```

## describe/contextの直下に変数や定数を定義しない

```ruby 
# NG
describe 'Foo spec' do
  name = 'Alice'

  before do
    FactoryBot.create(:user, name: name)
  end
  
  # ...
end
```

RSpecのDSLではdescribe/contextの直下に変数や定数を定義することを想定していません。
こうした変数や定数があるとテストコードが読みづらくなったり、予期しない挙動を招いたりする恐れがあります。

RSpecのDSLに沿った形で変数や定数を定義してください。

```ruby 
# OK
describe 'Foo spec' do
  before do
    name = 'Alice'
    FactoryBot.create(:user, name: name)
  end
  
  # ...
end
```

または

```ruby 
# OK
describe 'Foo spec' do
  # nameの値をbeforeブロック以外でも使いたい場合
  let(:name) { 'Alice' }
  
  before do
    FactoryBot.create(:user, name: name)
  end
  
  # ...
end
```

## まとめ
というわけで、僕がコードレビューしていて、「ん？いやいや、こんな書き方しちゃダメだよ！」と思ったコード例をまとめてみました。

冒頭に書いたとおり、ネタが増えたらまた追記するので、気になる方はこの記事のストックをお願いします〜！

Click here!👇
![Screenshot 2024-04-09 at 8.22.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/f4cfe0bd-39d8-fc7e-e39e-da4bc5bd5929.png)

## あわせて読みたい
https://qiita.com/jnchito/items/56ba52c9a7246fc5016a

## PR：Railsでテストコードが書けるようになりたいという人へ
DSLがどうこうという話をする前に、そもそもテストコードの書き方に自信がありません！😫 という方へ。

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

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

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