LoginSignup
23
21

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

Last updated at Posted at 2024-05-14

はじめに:これは何?

僕がコードレビューしていて、「ん?いやいや、こんな書き方しちゃダメだよ!」と思ったコード例をまとめた記事です。

この記事でフォーカスするのはRSpecのDSLを適切に構築してないケースです。

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

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

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

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

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

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

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

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

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

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

# NG
def foo
  # ...
end

describe 'Foo spec' do
  # ...
end

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

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

  # ...
end

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

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

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

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

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

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

  # ...
end

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

# 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は静的に構築するようにしてください。

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

  # ...
end

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

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

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

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

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

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

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

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

または

# 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

あわせて読みたい

PR:Railsでテストコードが書けるようになりたいという人へ

DSLがどうこうという話をする前に、そもそもテストコードの書き方に自信がありません!😫 という方へ。

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

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

23
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
21