Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What is going on with this article?
@yu_yukke

ネストしがちなRSpecの可読性を高める

More than 1 year has passed since last update.

はじめに

私はRailsを最近学びだして実際に簡単な業務からさせていただいている初心者です。
チュートリアルレベルの学んだことから、よりスキルアップするため
実際に動いているサービスのコードを触って気づいたことをまとめたいと思います。
あくまでまだまだ初心者である自分自身の理解を高めるためのアウトプットですので
間違いや勘違い等あればご指摘いただけますと幸いです。
また少しでも同じようなレベルの方の助けになればと思います。

ネストしがちなRSpecの可読性を高める

タイトル通りですが、(自分にとって)複雑なモデルやメソッドのRSpecを記述している際、どうしてもネストが深くなりすぎることがありました。
特に条件ごとに検証する際のdescribecontextあたりが多くなるとどんどん見づらくなって仕方ないので自分なりにまとめてみました。

同じ結果を条件ごとに検証するならsubjectにまとめる

例えばこのような、商品のnameが仮に10文字でバリデーションをかけている場合だとかは

product.spec
described 'product is valid?' do
 context '0文字の場合' do
  let(:product) { FactoryBot.create(:product, name: 'a' * 0)

  it { is_expected.to be_invalid }
 end

 context '9文字の場合' do
  let(:product) { FactoryBot.create(:product, name: 'a' * 9)

  it { is_expected.to be_invalid }
 end

 context '10文字の場合'do
  let(:product) { FactoryBot.create(:product, name: 'a' * 10)

  it { is_expected.to be_valid }
 end
end

のようにすべてのケースでproductを作成していくよりも、
結局検証したい結論(≒subject)はproductが保存できるかどうかなので

product.spec
described 'product is valid?' do
 subject(:product) { FactoryBot.create(:product, name: name) }

 context '0文字の場合' do
  let(:name) { '' }

  it { is_expected.to be_invalid }
 end

 context '9文字の場合' do
  let(:name) { 'a' * 9 }

  it { is_expected.to be_invalid }
 end

 context '10文字の場合'do
  let(:name) { 'a' * 10 }

  it { is_expected.to be_valid }
 end
end

のようにsubjectで事前に共通の処理を定義して、contextごとに条件を分けたい部分を渡していく方が見た目的にもスッキリする。

使い回す結果はshared_examplesにまとめて呼び出す

また上記のコードの中の

it { is_expected.to be_valid }
it { is_expected.to be_invalid }

これら含め、例えば

it { is_expected.to eq 200 }

だとか、正しくレスポンスを受けられているか等の結果は何かと使い回すことが多いので、そういった結果はshared_examplesにまとめて、it_behaves_likeで呼び出すと1つ1つのテストコード内での期待する結果がわかりやすい。
さっきのをさらに修正すると

product.spec
described 'product is valid?' do
 subject(:product) { FactoryBot.create(:product, name: name) }

 shared_examples '商品は保存できる' do
  it { is_expected.to be_valid }
 end

 shared_examples '商品は保存できない' do
  it { is_expected.to be_invalid }
 end

 context '0文字の場合' do
  let(:name) { '' }

  it_behaves_like '商品は保存できない'
 end

 context '9文字の場合' do
  let(:name) { 'a' * 9 }

  it_behaves_like '商品は保存できない'
 end

 context '10文字の場合'do
  let(:name) { 'a' * 10 }

  it_behaves_like '商品は保存できる'
 end
end

のように1つ1つのcontext内での条件と結果がわかりやすくなる。
今回は簡単なテストなのでこれだけですが、shared_examples内でもう少し検証が増える場合は

 shared_examples '商品は保存できる' do
  it '商品の持つ情報は妥当である' do
   is_expected.to be_valid
  end

  it '商品の名前がhoge' do
   is_expected.to hoge
  end
 end

 shared_examples '商品は保存できない' do
  it '商品の持つ情報は妥当ではない' do
   is_expected.to be_invalid
  end

  it '商品の名前がhoge' do
   is_expected.to hoge
  end
 end

のようにshared_examples内の検証にも文章入れた状態で呼び出せば、
shared_examplesの中のどの検証で落ちているのかエラーメッセージから読み取りやすくなる。
全体的に文章とかはざっくりアテているので適宜適切な文章に修正してください。


大体今気をつけていることはこの辺りかな、と思います。
手元でさっと検証できないので頭に入っていることを書き出したのですが間違い等あればご指摘ください。
またコントローラのrequest specとかでクエリパラメータが正しく渡っているか/渡っていないかのような検証時にも
今回のような条件分けをするとスッキリ見やすくて良かったです。
テストコードが綺麗になるだけでテストコードを書くモチベーションがかなり上がったのと、エラーを拾いやすくなったのでぜひ。

4
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
yu_yukke

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
4
Help us understand the problem. What is going on with this article?