5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

はじめに

私は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とかでクエリパラメータが正しく渡っているか/渡っていないかのような検証時にも
今回のような条件分けをするとスッキリ見やすくて良かったです。
テストコードが綺麗になるだけでテストコードを書くモチベーションがかなり上がったのと、エラーを拾いやすくなったのでぜひ。

5
4
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
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?