Readable RSpec

Readable RSpec

meguro.rb #4 @ Wantedly, Inc
読みやすいRSpecについての発表資料です。

88cc995ce5954b15c12ea14904258757?s=128

Kento Nagata

June 21, 2017
Tweet

Transcript

  1. Readable RSpec Kento Nagata / Wantedly Engineer

  2. ಡΈ΍͍͢RSpecʹ͍ͭͯ
 ͓࿩Λ͍ͨ͠ͱࢥ͍·͢ɻ

  3. ࣗݾ঺հ

  4. Ӭా݈ਓ 8BOUFEMZ *OD8FC&OHJOFFS 8PSL 8BOUFEMZ *OD 8FC&OHJOFFS 8BOUFEMZ7JTJUͷ։ൃվળ 8BOUFEMZ5PPMTͷ։ൃվળ 

    7JJCBS *OD 8FC&OHJOFFS ϚονϯάαʔϏε͓Αͼ੍࡞ࢧԉπʔϧͷ։ൃ  !OHULOU !OHUL
  5. ͦΕͰ͸ຊ୊ʹ
 ໭Γ·͢ɻ

  6. લఏ

  7. RSpec 3.6.0 FactoryGirl 4.8.0

  8. Table of contents * Describe and context * Use single-level

    contexts * Use let and before * Use subject and named subject * Reserved DRY * Expect change * Should not update context value * Set all significant fields on test data * Write expectations at a high level * Should not use "should" in our example names
  9. Describe and context

  10. Describe and context • describeͱcontextΛ࢖͍෼͚Δ • describe͸ςετର৅Λॻ͘ • context͸ςετ͕࣮ߦ͞ΕΔલఏ৚݅΍ঢ়ଶΛॻ͘

  11. RSpec.describe Book do describe '#reserve' do it 'raises already reserved

    error when already reserved' do # ... end it 'reserves the book when the book is available' do # ... end end end Describe and context RSpec.describe Book do describe '#reserve' do context 'when already reserved' do it 'raises already reserved error' do # ... end end context 'the book is available' do it 'reserves the book' do # ... end end end end Better /PUGood
  12. Use single-level contexts

  13. Use single-level contexts • ContextΛωετͤ͞ͳ͍

  14. RSpec.describe Book do describe '#reserve' do context 'when already reserved'

    do let(:reserved) { true } context 'when the users reservation limit is exceeded' do let(:exceeded) { true } # ... end context 'when the users reservation limit is not exceeded' do let(:exceeded) { false } # ... end end context 'the book is available' do let(:reserved) { false } context 'when the users reservation limit is exceeded' do let(:exceeded) { true } # ... end context 'when the users reservation limit is not exceeded' do let(:exceeded) { false } # ... end end end end Use single-level contexts RSpec.describe Book do describe '#reserve' do context 'when already reserved and the user reservation limit is exceeded' do let(:reserved) { true } let(:exceeded) { true } # ... end context 'when already reserved and the users reservation limit is not exceeded' do let(:reserved) { true } let(:exceeded) { false } # ... end context 'the book is available and the user reservation limit is exceeded' do let(:reserved) { false } let(:exceeded) { true } # ... end context 'the book is available and the user has enough reservation limit' do let(:reserved) { false } let(:exceeded) { false } # ... end end end Better /PUGood
  15. RSpec.describe Book do describe '#reserve' do context 'when already reserved'

    do let(:reserved) { true } context 'when the users reservation limit is exceeded' do let(:exceeded) { true } # ... end context 'when the users reservation limit is not exceeded' do let(:exceeded) { false } # ... end end context 'the book is available' do let(:reserved) { false } context 'when the users reservation limit is exceeded' do let(:exceeded) { true } # ... end context 'when the users reservation limit is not exceeded' do let(:exceeded) { false } # ... end end end end Use single-level contexts RSpec.describe Book do describe '#reserve' do context 'when already reserved and the user reservation limit is exceeded' do let(:reserved) { true } let(:exceeded) { true } # ... end context 'when already reserved and the users reservation limit is not exceeded' do let(:reserved) { true } let(:exceeded) { false } # ... end context 'the book is available and the user reservation limit is exceeded' do let(:reserved) { false } let(:exceeded) { true } # ... end context 'the book is available and the user has enough reservation limit' do let(:reserved) { false } let(:exceeded) { false } # ... end end end Better /PUGood RSpec.describe Book do describe '#reserve' do context 'when already reserved' do let(:reserved) { true } context 'when the users reservation limit is exceeded' do let(:exceeded) { true } # ... end end end /PUGood
  16. RSpec.describe Book do describe '#reserve' do context 'when already reserved'

    do let(:reserved) { true } context 'when the users reservation limit is exceeded' do let(:exceeded) { true } # ... end context 'when the users reservation limit is not exceeded' do let(:exceeded) { false } # ... end end context 'the book is available' do let(:reserved) { false } context 'when the users reservation limit is exceeded' do let(:exceeded) { true } # ... end context 'when the users reservation limit is not exceeded' do let(:exceeded) { false } # ... end end end end Use single-level contexts RSpec.describe Book do describe '#reserve' do context 'when already reserved and the user reservation limit is exceeded' do let(:reserved) { true } let(:exceeded) { true } # ... end context 'when already reserved and the users reservation limit is not exceeded' do let(:reserved) { true } let(:exceeded) { false } # ... end context 'the book is available and the user reservation limit is exceeded' do let(:reserved) { false } let(:exceeded) { true } # ... end context 'the book is available and the user has enough reservation limit' do let(:reserved) { false } let(:exceeded) { false } # ... end end end Better /PUGood RSpec.describe Book do describe '#reserve' do context 'when already reserved and the user reservation limit is exceeded' do let(:reserved) { true } let(:exceeded) { true } # ... end end end #FUUFS
  17. RSpec.describe Book do describe '#reserve' do context 'when already reserved'

    do let(:reserved) { true } context 'when the users reservation limit is exceeded' do let(:exceeded) { true } # ... end context 'when the users reservation limit is not exceeded' do let(:exceeded) { false } # ... end end context 'the book is available' do let(:reserved) { false } context 'when the users reservation limit is exceeded' do let(:exceeded) { true } # ... end context 'when the users reservation limit is not exceeded' do let(:exceeded) { false } # ... end end end end Use single-level contexts RSpec.describe Book do describe '#reserve' do context 'when already reserved and the user reservation limit is exceeded' do let(:reserved) { true } let(:exceeded) { true } # ... end context 'when already reserved and the users reservation limit is not exceeded' do let(:reserved) { true } let(:exceeded) { false } # ... end context 'the book is available and the user reservation limit is exceeded' do let(:reserved) { false } let(:exceeded) { true } # ... end context 'the book is available and the user has enough reservation limit' do let(:reserved) { false } let(:exceeded) { false } # ... end end end Better /PUGood RSpec.describe Book do describe '#reserve' do context 'when already reserved and the user reservation limit is exceeded' do let(:reserved) { true } let(:exceeded) { true } # ... end context 'when already reserved and the users reservation limit is not exceeded' do let(:reserved) { true } let(:exceeded) { false } # ... end end end #FUUFS
  18. Use let and before

  19. Use let and before • letͱbeforeΛ࢖ͬͯcontextΛ໌֬ʹ͢Δ • context ϒϩοΫͷ௚Լʹॻ͘ •

    ੜ੒ޙʹࢀর͢ΔΦϒδΣΫτ͸letΛ࢖͏ • ੜ੒ޙʹࢀর͠ͳ͍ΦϒδΣΫτ͸beforeΛ࢖͏ • Πϯελϯεม਺͸࢖Θͳ͍ • typoͯ͠΋ؾ෇͖ͮΒ͍ • `before(:each)`͸ͲͷexampleͰ΋૸ΔͨΊແବʹΞαΠϯ͞ΕΔ • ϩʔΧϧม਺ͱࠞࡏ͢ΔͱϦϑΝΫλͮ͠Β͍
  20. # Case 1 RSpec.describe Book do describe '#reserve' do context

    'when the book is already reserved' do it 'returns falsey value' do reserved = true book = Book.new(reserved: reserved) book.reserve expect(book.reserved).to be_falsey end end end end # Case 2 RSpec.describe Book do describe '#reserve' do context 'when the book is already reserved' do before do @reserved = true @book = Book.new(reserved: reserved) @book.reserve end it 'returns falsey value' do expect(@book.reserve).to be_falsey end end end end Use let and before RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(reserved: reserved) } context 'when the book is already reserved' do let(:reserved) { true } it 'returns falsey value' do is_expected.to be_falsey end end end end Better /PUGood
  21. # Case 1 RSpec.describe Book do describe '#reserve' do context

    'when the book is already reserved' do it 'returns falsey value' do reserved = true book = Book.new(reserved: reserved) book.reserve expect(book.reserved).to be_falsey end end end end # Case 2 RSpec.describe Book do describe '#reserve' do context 'when the book is already reserved' do before do @reserved = true @book = Book.new(reserved: reserved) @book.reserve end it 'returns falsey value' do expect(@book.reserve).to be_falsey end end end end Use let and before RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(reserved: reserved) } context 'when the book is already reserved' do let(:reserved) { true } it 'returns falsey value' do is_expected.to be_falsey end end end end Better /PUGood # Case 1 RSpec.describe Book do describe '#reserve' do context 'when the book is already reserved' do it 'returns falsey value' do reserved = true book = Book.new(reserved: reserved) book.reserve expect(book.reserved).to be_falsey end end end end /PUGood DPOUFYU͕Կ͔͕Θ͔ΓͮΒ͍
  22. # Case 1 RSpec.describe Book do describe '#reserve' do context

    'when the book is already reserved' do it 'returns falsey value' do reserved = true book = Book.new(reserved: reserved) book.reserve expect(book.reserved).to be_falsey end end end end # Case 2 RSpec.describe Book do describe '#reserve' do context 'when the book is already reserved' do before do @reserved = true @book = Book.new(reserved: reserved) @book.reserve end it 'returns falsey value' do expect(@book.reserve).to be_falsey end end end end Use let and before RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(reserved: reserved) } context 'when the book is already reserved' do let(:reserved) { true } it 'returns falsey value' do is_expected.to be_falsey end end end end Better /PUGood /PUGood DPOUFYU͕Կ͔͕Θ͔ΓͮΒ͍ # Case 1 RSpec.describe Book do describe '#reserve' do context 'when the book is already reserved' do it 'returns falsey value' do reserved = true book = Book.new(reserved: reserved) book.reserve expect(book.reserved).to be_falsey end end end end ͕͜͜DPOUFYU
  23. # Case 1 RSpec.describe Book do describe '#reserve' do context

    'when the book is already reserved' do it 'returns falsey value' do reserved = true book = Book.new(reserved: reserved) book.reserve expect(book.reserved).to be_falsey end end end end # Case 2 RSpec.describe Book do describe '#reserve' do context 'when the book is already reserved' do before do @reserved = true @book = Book.new(reserved: reserved) @book.reserve end it 'returns falsey value' do expect(@book.reserve).to be_falsey end end end end Use let and before RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(reserved: reserved) } context 'when the book is already reserved' do let(:reserved) { true } it 'returns falsey value' do is_expected.to be_falsey end end end end Better /PUGood # Case 2 RSpec.describe Book do describe '#reserve' do context 'when the book is already reserved' do before do @reserved = true @book = Book.new(reserved: reserved) @book.reserve end it 'returns falsey value' do expect(@book.reserve).to be_falsey end end end end /PUGood *OTUBODFWBSJBCMFΛ࢖Θͳ͍
  24. # Case 1 RSpec.describe Book do describe '#reserve' do context

    'when the book is already reserved' do it 'returns falsey value' do reserved = true book = Book.new(reserved: reserved) book.reserve expect(book.reserved).to be_falsey end end end end # Case 2 RSpec.describe Book do describe '#reserve' do context 'when the book is already reserved' do before do @reserved = true @book = Book.new(reserved: reserved) @book.reserve end it 'returns falsey value' do expect(@book.reserve).to be_falsey end end end end Use let and before RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(reserved: reserved) } context 'when the book is already reserved' do let(:reserved) { true } it 'returns falsey value' do is_expected.to be_falsey end end end end Better /PUGood /PUGood Πϯελϯεม਺Λ࢖Θͳ͍ # Case 2 RSpec.describe Book do describe '#reserve' do context 'when the book is already reserved' do before do @reserved = true @book = Book.new(reserved: reserved) @book.reserve end it 'returns falsey value' do expect(@book.reserve).to be_falsey end end end end FYBNQMFຖͰݺ͹Εͯ͠·͏ ϩʔΧϧม਺ͱͷ۠ผ͕ ೉͘͠ͳΔ
  25. RSpec.describe Book do describe '#reserve' do context 'when the book

    is already reserved' do it 'returns falsey value' do reserved = true book = Book.new(reserved: reserved) book.reserve expect(book.reserved).to be_truthy end end end end RSpec.describe Book do describe '#reserve' do before do @reserved = true @book = Book.new(reserved: reserved) @book.reserve end it 'returns falsey value' do expect(@book.reserve).to be_falsey end end end Use let and before RSpec.describe Book do describe '#reserve' do context 'when already reserved and the user reservation limit is exceeded' do let(:reserved) { true } let(:exceeded) { true } # ... end context 'when already reserved and the users reservation limit is not exceeded' do let(:reserved) { true } let(:exceeded) { false } # ... end context 'the book is available and the user reservation limit is exceeded' do let(:reserved) { false } let(:exceeded) { true } # ... end context 'the book is available and the user has enough reservation limit' do let(:reserved) { false } let(:exceeded) { false } # ... end end end Better /PUGood RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(reserved: reserved) } context 'when the book is already reserved' do let(:reserved) { true } it 'returns falsey value' do is_expected.to be_falsey end end end end #FUUFS
  26. RSpec.describe Book do describe '#reserve' do context 'when the book

    is already reserved' do it 'returns falsey value' do reserved = true book = Book.new(reserved: reserved) book.reserve expect(book.reserved).to be_truthy end end end end RSpec.describe Book do describe '#reserve' do before do @reserved = true @book = Book.new(reserved: reserved) @book.reserve end it 'returns falsey value' do expect(@book.reserve).to be_falsey end end end Use let and before RSpec.describe Book do describe '#reserve' do context 'when already reserved and the user reservation limit is exceeded' do let(:reserved) { true } let(:exceeded) { true } # ... end context 'when already reserved and the users reservation limit is not exceeded' do let(:reserved) { true } let(:exceeded) { false } # ... end context 'the book is available and the user reservation limit is exceeded' do let(:reserved) { false } let(:exceeded) { true } # ... end context 'the book is available and the user has enough reservation limit' do let(:reserved) { false } let(:exceeded) { false } # ... end end end Better /PUGood #FUUFS RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(reserved: reserved) } context 'when the book is already reserved' do let(:reserved) { true } it 'returns falsey value' do is_expected.to be_falsey end end end end DPOUFYUͱMFU͕ରԠ͍ͯͯ͠
 ಡΈ΍͍͢
  27. Use subject and named subject

  28. Use subject and named subject • ςετର৅ͷΦϒδΣΫτ΍ϝιουΛsubjectͰ໌֬ʹ͢Δ • ςετର৅ʹ෭࡞༻͕͋Γɺͦͷ෭࡞༻Λςετ͢Δ৔߹
 subjectʹ໊લΛ͚ͭͱಡΈ΍͘͢ͳΔ

  29. RSpec.describe Book do describe '#reserve' do let(:book) { Book.new(reserved: false)

    } let(:user) { User.new } it 'returns truthy value' do expect(book.reserve(user)).to be_truthy end it 'reserved user returns correct user' do book.reserve(user) expect(book.reserved_user).to eq user end end end Use subject and named subject RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new(reserved: false) } let(:user) { User.new } it 'returns truthy value' do is_expected.to be_truthy end it 'reserved user returns correct user' do reserve_book expect(book.reserved_user).to eq user end it 'number of books that the user reserved increases by 1' do expect { reserve_book }.to change { user.reserved_books.count }.by(1) end end end Better /PUGood
  30. RSpec.describe Book do describe '#reserve' do let(:book) { Book.new(reserved: false)

    } let(:user) { User.new } it 'returns truthy value' do expect(book.reserve(user)).to be_truthy end it 'reserved user returns correct user' do book.reserve(user) expect(book.reserved_user).to eq user end end end Use subject and named subject RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new(reserved: false) } let(:user) { User.new } it 'returns truthy value' do is_expected.to be_truthy end it 'reserved user returns correct user' do reserve_book expect(book.reserved_user).to eq user end it 'number of books that the user reserved increases by 1' do expect { reserve_book }.to change { user.reserved_books.count }.by(1) end end end Better /PUGood /PUGood ςετओମ͕Θ͔ΓͮΒ͍ RSpec.describe Book do describe '#reserve' do let(:book) { Book.new(reserved: false) } let(:user) { User.new } it 'returns truthy value' do expect(book.reserve(user)).to be_truthy end it 'reserved user returns correct user' do book.reserve(user) expect(book.reserved_user).to eq user end end end
  31. RSpec.describe Book do describe '#reserve' do let(:book) { Book.new(reserved: false)

    } let(:user) { User.new } it 'returns truthy value' do expect(book.reserve(user)).to be_truthy end it 'reserved user returns correct user' do book.reserve(user) expect(book.reserved_user).to eq user end end end Use subject and named subject RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new(reserved: false) } let(:user) { User.new } it 'returns truthy value' do is_expected.to be_truthy end it 'reserved user returns correct user' do reserve_book expect(book.reserved_user).to eq user end it 'number of books that the user reserved increases by 1' do expect { reserve_book }.to change { user.reserved_books.count }.by(1) end end end Better /PUGood #FUUFS RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(reserved: false) } let(:user) { User.new } it 'returns truthy value' do is_expected.to be_truthy end it 'reserved user returns correct user' do subject expect(book.reserved_user).to eq user end end end ςετओମ͕໌͔֬ͭ%3:
  32. RSpec.describe Book do describe '#reserve' do let(:book) { Book.new(reserved: false)

    } let(:user) { User.new } it 'returns truthy value' do expect(book.reserve(user)).to be_truthy end it 'reserved user returns correct user' do book.reserve(user) expect(book.reserved_user).to eq user end end end Use subject and named subject RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new(reserved: false) } let(:user) { User.new } it 'returns truthy value' do is_expected.to be_truthy end it 'reserved user returns correct user' do reserve_book expect(book.reserved_user).to eq user end it 'number of books that the user reserved increases by 1' do expect { reserve_book }.to change { user.reserved_books.count }.by(1) end end end Better /PUGood #FUUFS✨ RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new(reserved: false) } let(:user) { User.new } it 'reserved user returns correct user' do reserve_book expect(book.reserved_user).to eq user end end end ओޠ͕ԿΛҙຯ͢Δͷ͔͕໌֬ʹͳΔ
  33. RSpec.describe Book do describe '#reserve' do let(:book) { Book.new(reserved: false)

    } let(:user) { User.new } it 'returns truthy value' do expect(book.reserve(user)).to be_truthy end it 'reserved user returns correct user' do book.reserve(user) expect(book.reserved_user).to eq user end end end Use subject and named subject RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new(reserved: false) } let(:user) { User.new } it 'returns truthy value' do is_expected.to be_truthy end it 'reserved user returns correct user' do reserve_book expect(book.reserved_user).to eq user end it 'number of books that the user reserved increases by 1' do expect { reserve_book }.to change { user.reserved_books.count }.by(1) end end end Better /PUGood #FUUFS✨ DIBOHFFYQFDUBUJPO͸ಛʹݟ΍͍͢ RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new(reserved: false) } let(:user) { User.new } it 'number of books that the user reserved increases by 1' do expect { reserve_book }.to change { user.reserved_books.count }.by(1) end end end
  34. Reserved DRY

  35. Reserved DRY • DRY(Don’t Repeat Yourself)͸߇͑Ίʹ͢Δ • ৑௕ੑʹΑΔมߋͷͮ͠Β͞ΑΓɺ
 ಡΈʹ͘͞ͷํ͕໰୊ʹͳΔέʔε͕ଟ͍ •

    shared example΍macroΛ࢖͏৔߹
 ͦͷந৅ԽʹΑͬͯಘΒΕͨ΋ͷͱՄಡੑͷόϥϯεΛߟ͑Δ΂͖
  36. RSpec.shared_examples 'reservation' do |decrease_by:, has_due_date:| it "reserve the book and

    decrease remaining by -#{decrease_by}" do expect { reserve_book }.to change { user.remaining_count }.by(-decrease_by) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be has_due_date end end RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new } context 'when user has admin role' do let(:user) { User.new(role: [:admin]) } it_bahaves_like "reservation", decrease_by: 0, has_due_date: false end context 'when user has normal role' do let(:user) { User.new(role: [:normal]) } it_bahaves_like "reservation", decrease_by: 1, has_due_date: true end end end Reserved DRY RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new } context 'when user has admin role' do let(:user) { User.new(role: [:admin]) } it "reserve and decrease remaining count and has no due date" do expect { reserve_book }.not_to change { user.remaining_count } expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be_falsey end end context 'when user has normal role' do let(:user) { User.new(role: [:normal]) } it "reserve and decrease remaining count and has no due date" do expect { reserve_book }.to change { user.remaining_count }.by(1) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be_truthy end end end end Better /PUGood
  37. RSpec.shared_examples 'reservation' do |decrease_by:, has_due_date:| it "reserve the book and

    decrease remaining by -#{decrease_by}" do expect { reserve_book }.to change { user.remaining_count }.by(-decrease_by) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be has_due_date end end RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new } context 'when user has admin role' do let(:user) { User.new(role: [:admin]) } it_bahaves_like "reservation", decrease_by: 0, has_due_date: false end context 'when user has normal role' do let(:user) { User.new(role: [:normal]) } it_bahaves_like "reservation", decrease_by: 1, has_due_date: true end end end Reserved DRY RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new } context 'when user has admin role' do let(:user) { User.new(role: [:admin]) } it "reserve and decrease remaining count and has no due date" do expect { reserve_book }.not_to change { user.remaining_count } expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be_falsey end end context 'when user has normal role' do let(:user) { User.new(role: [:normal]) } it "reserve and decrease remaining count and has no due date" do expect { reserve_book }.to change { user.remaining_count }.by(1) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be_truthy end end end end Better /PUGood /PUGood ඞཁͳҾ਺ɺ৚͕݅Θ͔ΓͮΒ͍ RSpec.shared_examples 'reservation' do |decrease_by:, has_due_date:| it "reserve the book and decrease remaining by -#{decrease_by}" do expect { reserve_book }.to change { user.remaining_count }.by(-decrease_by) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be has_due_date end end
  38. RSpec.shared_examples 'reservation' do |decrease_by:, has_due_date:| it "reserve the book and

    decrease remaining by -#{decrease_by}" do expect { reserve_book }.to change { user.remaining_count }.by(-decrease_by) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be has_due_date end end RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new } context 'when user has admin role' do let(:user) { User.new(role: [:admin]) } it_bahaves_like "reservation", decrease_by: 0, has_due_date: false end context 'when user has normal role' do let(:user) { User.new(role: [:normal]) } it_bahaves_like "reservation", decrease_by: 1, has_due_date: true end end end Reserved DRY RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new } context 'when user has admin role' do let(:user) { User.new(role: [:admin]) } it "reserve and decrease remaining count and has no due date" do expect { reserve_book }.not_to change { user.remaining_count } expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be_falsey end end context 'when user has normal role' do let(:user) { User.new(role: [:normal]) } it "reserve and decrease remaining count and has no due date" do expect { reserve_book }.to change { user.remaining_count }.by(1) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be_truthy end end end end Better /PUGood /PUGood ඞཁͳҾ਺ɺ৚͕݅Θ͔ΓͮΒ͍ RSpec.shared_examples 'reservation' do |decrease_by:, has_due_date:| it "reserve the book and decrease remaining by -#{decrease_by}" do expect { reserve_book }.to change { user.remaining_count }.by(-decrease_by) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be has_due_date end end
  39. RSpec.shared_examples 'reservation' do |decrease_by:, has_due_date:| it "reserve the book and

    decrease remaining by -#{decrease_by}" do expect { reserve_book }.to change { user.remaining_count }.by(-decrease_by) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be has_due_date end end RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new } context 'when user has admin role' do let(:user) { User.new(role: [:admin]) } it_bahaves_like "reservation", decrease_by: 0, has_due_date: false end context 'when user has normal role' do let(:user) { User.new(role: [:normal]) } it_bahaves_like "reservation", decrease_by: 1, has_due_date: true end end end Reserved DRY RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new } context 'when user has admin role' do let(:user) { User.new(role: [:admin]) } it "reserve and decrease remaining count and has no due date" do expect { reserve_book }.not_to change { user.remaining_count } expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be_falsey end end context 'when user has normal role' do let(:user) { User.new(role: [:normal]) } it "reserve and decrease remaining count and has no due date" do expect { reserve_book }.to change { user.remaining_count }.by(1) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be_truthy end end end end Better /PUGood /PUGood ඞཁͳҾ਺ɺ৚͕݅Θ͔ΓͮΒ͍ RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new } context 'when user has admin role' do let(:user) { User.new(role: [:admin]) } it_bahaves_like "reservation", decrease_by: 0, has_due_date: false end context 'when user has normal role' do let(:user) { User.new(role: [:normal]) } it_bahaves_like "reservation", decrease_by: 1, has_due_date: true end end end
  40. RSpec.shared_examples 'reservation' do |decrease_by:, has_due_date:| it "reserve the book and

    decrease remaining by -#{decrease_by}" do expect { reserve_book }.to change { user.remaining_count }.by(-decrease_by) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be has_due_date end end RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new } context 'when user has admin role' do let(:user) { User.new(role: [:admin]) } it_bahaves_like "reservation", decrease_by: 0, has_due_date: false end context 'when user has normal role' do let(:user) { User.new(role: [:normal]) } it_bahaves_like "reservation", decrease_by: 1, has_due_date: true end end end Reserved DRY RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new } context 'when user has admin role' do let(:user) { User.new(role: [:admin]) } it "reserve and decrease remaining count and has no due date" do expect { reserve_book }.not_to change { user.remaining_count } expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be_falsey end end context 'when user has normal role' do let(:user) { User.new(role: [:normal]) } it "reserve and decrease remaining count and has no due date" do expect { reserve_book }.to change { user.remaining_count }.by(1) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be_truthy end end end end Better /PUGood #FUUFS ৑௕ʹॻ͍ͨ΄͏͕ඞཁͳ৚݅΍ظ଴஋͕෼͔Γ΍͍͢ RSpec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve } let(:book) { Book.new } context 'when user has admin role' do let(:user) { User.new(role: [:admin]) } it "reserve and decrease remaining count and has no due date" do expect { reserve_book }.not_to change { user.remaining_count } expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be_falsey end end context 'when user has normal role' do let(:user) { User.new(role: [:normal]) } it "reserve and decrease remaining count and has no due date" do expect { reserve_book }.to change { user.remaining_count }.by(1) expect(book.reserved_user).to eq user expect(book.reservation.due_date.present?).to be_truthy end end end end
  41. Expect change

  42. Expect change • ʮมߋʯʹ͍ͭͯςετΛ͢Δ৔߹ɺexpect changeΛ࢖͏ • ෭࡞༻ʹ͍ͭͯςετ͍ͯ͠Δ͜ͱ͕໌֬ʹͳΔ • expect changeΛ࢖Θͳ͍৔߹ɺҙਤͤͣલఏ͕յΕͯ͠·͏Մೳੑ͕͋Δ

  43. Rspec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve(user) }

    let!(:user) { User.new(remaining_count: 3) } it 'decrease remaining count by 1' do reserve_book expect(user.remaining_count).to eq 2 end end end Expect change Rspec.describe Book do describe '#reserve' do subject(:reserve_book) { book.reserve(user) } let!(:user) { User.new(remaining_count: 3) } it 'decrease remaining count by 1' do expect { reserve_book }.to change { user.remaining_count }.by(-1) end end end Better /PUGood
  44. Should not update context value

  45. Should not update context value • contextͷதͰbefore΍letͰࣄલʹఆٛͨ͠஋Λߋ৽͠ͳ͍ • ࠷ऴతͳ஋Λ೺Ѳ͢Δͷ͕೉͘͠ͳΔ

  46. # Case 1 RSpec.describe Book do describe '#reserve' do subject

    { book.reserve } let(:book) { Book.new(available: true) } context 'when the book is not available' do before do book.update(available: false) end # ... end end end # Case 2 RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: available, country: country) } let(:available) { true } let(:country) { :jp } context 'when the book is not available' do let(:available) { false } # ... end end end Should not update context value RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: available, country: :jp) } context 'when the book is not available' do let(:available) { false } # ... end end end Better /PUGood
  47. # Case 1 RSpec.describe Book do describe '#reserve' do subject

    { book.reserve } let(:book) { Book.new(available: true) } context 'when the book is not available' do before do book.update(available: false) end # ... end end end # Case 2 RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: available, country: country) } let(:available) { true } let(:country) { :jp } context 'when the book is not available' do let(:available) { false } # ... end end end Should not update context value RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: available, country: :jp) } context 'when the book is not available' do let(:available) { false } # ... end end end Better /PUGood /PUGood ͦͷ$POUFYUͰͷ࠷ऴతͳ஋Λཧղͮ͠Β͘ͳΔ # Case 1 RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: true) } context 'when the book is not available' do before do book.update(available: false) end # ... end end end
  48. # Case 1 RSpec.describe Book do describe '#reserve' do subject

    { book.reserve } let(:book) { Book.new(available: true) } context 'when the book is not available' do before do book.update(available: false) end # ... end end end # Case 2 RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: available, country: country) } let(:available) { true } let(:country) { :jp } context 'when the book is not available' do let(:available) { false } # ... end end end Should not update context value RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: available, country: :jp) } context 'when the book is not available' do let(:available) { false } # ... end end end Better /PUGood /PUGood MFUͰ্ॻ͖͢Δ৔߹΋ಉ͡໰୊͕ى͖Δ # Case 2 RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: available, country: country) } let(:available) { true } let(:country) { :jp } context 'when the book is not available' do let(:available) { false } # ... end end end
  49. # Case 1 RSpec.describe Book do describe '#reserve' do subject

    { book.reserve } let(:book) { Book.new(available: true) } context 'when the book is not available' do before do book.update(available: false) end # ... end end end # Case 2 RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: available, country: country) } let(:available) { true } let(:country) { :jp } context 'when the book is not available' do let(:available) { false } # ... end end end Should not update context value RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: available, country: :jp) } context 'when the book is not available' do let(:available) { false } # ... end end end Better /PUGood #FUUFS DPOUFYUͰࢦఆ͢Δ஋͸σϑΥϧτ஋Λઃఆ͠ͳ͍ RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: available, country: :jp) } context 'when the book is not available' do let(:available) { false } # ... end end end
  50. Write expectations at a high level

  51. Write expectations at a high level • context΍exampleͷઆ໌͸ίʔυϨϕϧͷઆ໌Ͱͳ͘ɺ
 ΑΓߴڃతͳઆ໌Λॻ͘ •

    ίʔυϨϕϧͷઆ໌͸contextϒϩοΫͷlet΍beforeΛݟΕ͹෼͔Δ
  52. Write expectations at a high level Better /PUGood RSpec.describe Book

    do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: available) } context 'when book.available == false' do let(:available) { false } # ... end end end RSpec.describe Book do describe '#reserve' do subject { book.reserve } let(:book) { Book.new(available: available) } context 'when the book can be reserved' do let(:available) { false } # ... end end end
  53. Should not use “should” in example names

  54. Should not use “should” in example names • exampleͷઆ໌ʹ͸shouldΛ࢖Θͣɺݴ͍੾Δ •

    ݴ͍੾Γେࣄ • ʮόά͕͋Γ·͕ͨ͠ɺ΋͏͋Γ·ͤΜʯ by tumblr
  55. it 'is reserved' it 'should be reserved' Should not use

    “should” in example names Better /PUGood
  56. Summary * Describe and context * Use single-level contexts *

    Use let and before * Use subject and named subject * Reserved DRY * Expect change * Should not update context value * Write expectations at a high level * Should not use "should" in our example names
  57. ·ͩ·ͩ͋Γ·͕͢ɺ
 Ұ൪جຊͦ͏ͳͱ͜ΖΛ
 ·ͱΊ·ͨ͠ʂ

  58. օ͞Μ͕࣮ફ͍ͯ͠Δ
 ಡΈ΍͍͢ॻ͖ํ ੋඇڭ͍͑ͯͩ͘͞ʂ

  59. Happy testing