Edited at

rspecの悪い書き方、良い書き方

More than 1 year has passed since last update.


ケース1


悪い書き方: itに文脈を書いてしまっている。

describe '#hoge' do

it '引数がfooの場合barを返す' do
expect(hoge('foo')).to eq 'bar'
end
end


良い書き方: contextを使いましょう。itには期待値だけを書きましょう。

describe '#hoge' do

context '引数がfooの場合' do
let(:args) { 'foo' }
it 'barを返す' do
expect(hoge(args)).to eq 'bar'
end
end
end


ケース2


あまり良くない書き方: itsを使っていない

describe '#hoge' do

it { expecte(hoge).to eq 'bar' }
end


良い書き方: itsを使いましょう。

its(:hoge) { is_expected.to eq 'bar' }

(itsを使うためにはrspec-its gemが必要です。)


ケース3


悪い書き方: contextの内容が直下のbeforeの内容と違う。

context 'ログインしている場合' do

it '名前が表示される' do
login(user)
is_expected.to have_content 'Alice'
end
end


良い書き方: beforeを使ってcontextの内容とbeforeの内容が同じになるようにしましょう。

context 'ログインしている場合' do

before { login(user) }
it '名前が表示される' do
is_expected.to have_content 'Alice'
end
end


ケース4


悪い書き方: letを使っていない。

describe User do

before { @user = create(:user, name: 'Alice') }

it '#name' do
expect(@user.name).to eq 'Alice'
end
end


良い書き方: letを使いましょう。

describe User do

let(:user) { create(:user, name: 'Alice') }

it '#name' do
expect(user.name).to eq 'Alice'
end
end


ケース5


悪い書き方: describesubjectが一致しない(subjectを使っていない)。

describe User do

let(:user) { create(:user, name: 'Alice') }

it '#name' do
expect(user.name).to eq 'Alice'
end
end


良い書き方: describeの内容とsubjectは一致させましょう。

describe User do

let(:user) { create(:user, name: 'Alice') }
subject { user }

its(:name) { is_expected.to eq 'Alice' }
end

これでitsも使えるようになります。


ケース6


悪い書き方: factory_girlのリレーションを使っていない。


user_factory.rb

FactoryGirl.define do

factory :user do
company_id { rand(1..100) }
name { Faker::Name.name }
end
end

let(:company) { create(:company) }

let(:user) { create(:user, company_id: company.id) }


良い書き方: リレーションを使いましょう。


user_factory.rb

FactoryGirl.define do

factory :user do
company
name { Faker::Name.name }
end
end

let(:company) { create(:company) }

let(:user) { create(:user, company: company) }


ケース7


あまり良くない書き方: shouldaを使っていない。

context 'keyが空の場合' do

before { user.key = '' }
it { is_expected.to be_invalid }
end


良い書き方: shouldaを使いましょう。

it { is_expected.to validate_presence_of(:key) }

他に何ができるかは https://github.com/thoughtbot/shoulda-matchers を見てください。

かなり色々できます。


ケース8


悪い書き方: shouldを使っている。

it { foo.should eq 'bar' }


良い書き方: expectを使いましょう。

it { expect(foo).to eq 'bar' }


ケース9


あまり良くない書き方: 重いテストをまとめていない。

RSpec.feature 'Project List Page', js: true do

before { visit project_list_path }
subject { page }

it { is_expected.to have_content 'ID' }
it { is_expected.to have_content '名前' }
it { is_expected.to have_content '受注先会社名' }
it { is_expected.to have_content '開始日' }
it { is_expected.to have_content '終了日' }
it { is_expected.to have_content '契約日' }
end


良い書き方: 重いテストは1つにまとめましょう

「1つのテストで確認することは1つだけ」は正しいことですが、テストの実行にかかる時間とトレードオフです。

仮想ブラウザを使ったテストなどの重いテストはまとめましょう。

RSpec.feature 'Project List Page', js: true do

before { visit project_list_path }
subject { page }

it '各項目が存在すること' do
is_expected.to have_content 'ID'
is_expected.to have_content '名前'
is_expected.to have_content '受注先会社名'
is_expected.to have_content '開始日'
is_expected.to have_content '終了日'
is_expected.to have_content '契約日'
end
end


aggregate_failuresの使用

上記の例について、aggregate_failuresで各expectを囲ってやることでテストがfailした時にどの項目で落ちたのかがわかるようになります。

RSpec.feature 'Project List Page', js: true do

before { visit project_list_path }
subject { page }

it '各項目が存在すること' do
aggregate_failures 'testing items' do
is_expected.to have_content 'ID'
is_expected.to have_content '名前'
is_expected.to have_content '受注先会社名'
is_expected.to have_content '開始日'
is_expected.to have_content '終了日'
is_expected.to have_content '契約日'
end
end
end

spec_helperに以下の記述を追加することでも同じことが可能です。

config.define_derived_metadata do |meta|

meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
end


まとめ的な

specとは仕様という意味です。つまり、rspecは仕様書です。

その点をふまえて以下の点を気をつけるといいと思います。



  • context,describeを正しく使って仕様書として読みやすくすること


  • context,describeで謳っていることと、before,subject,it内のテストコードの内容が一致すること/一致することがすぐわかること

上記を守って楽しくプログラミング!