Slide 1

Slide 1 text

Clean Test Code @willnet

Slide 2

Slide 2 text

ࣗݾ঺հ • લౡਅҰ aka @willnet or @netwillnet • ginza.rb ͔Βདྷ·ͨ͠ • ϑϦʔϥϯεRailsٕज़ސ໰ • https://github.com/willnet • https://twitter.com/netwillnet • http://blog.willnet.in

Slide 3

Slide 3 text

ٕज़ސ໰ۀͬͯͳʹ΍ͬͯΔͷ Railsͷ஌ݟΛձࣾʹఏڙ͢Δ࢓ࣄ • ձࣾ૑ੈظʹෛ࠴ΛͨΊͯ͠·͕ͬͨͦΖͦΖฦ͍ͨ͠ • ผͷݴޠͰ։ൃ͍͕ͯͨ͠Rubyʹ৐Γ׵͍͑ͨ • ελʔτ͔࣌Βෛ࠴ΛஷΊͣʹRailsΞϓϦέʔγϣϯΛ։ൃ ͍ͨ͠

Slide 4

Slide 4 text

۩ମతʹԿ΍ͬͯΔͷ • Rails΍γεςϜઃܭʹؔ͢Δ࣭໰ʹ౴͑Δ • ίʔυϨϏϡʔ • ࣾ಺ษڧձͷ։࠵ • ίʔυΛॻ͘ • ࠾༻ͷ૬ஊ৐Δ • etc

Slide 5

Slide 5 text

Railsͷ஌ݟΛෳ਺ͷձ ࣾʹఏڙ࢝͠Ίͯ1೥൒ ͘Β͍

Slide 6

Slide 6 text

৭ΜͳॴͰಉ͜͡ͱݴͬ ͯΔؾ͕͢Δͧʁ

Slide 7

Slide 7 text

Α͘આ໌͢Δ಺༰Λ·ͱ ΊΔͱ…

Slide 8

Slide 8 text

ʮ͜ΕಡΜͰ͓͍ͯͩ͘ ͍͞ʯͰऴΘͬͯ΂Μ Γʂ

Slide 9

Slide 9 text

Α͘આ໌͢Δ಺༰Λ·ͱΊͨྫ

Slide 10

Slide 10 text

ࠓճ͸ςετίʔυͷॻ ͖ํʹ͍ͭͯ·ͱΊ·͠ ͨ

Slide 11

Slide 11 text

ͳͥςετίʔυ͔ ΞϓϦέʔγϣϯίʔυʹ͸RubyͰՄಡੑΛอͭͨΊͷํ๏ɺ RailsͰՄಡੑΛอͭͨΊͷํ๏ͱ΋ʹॻ੶΍ωοτهࣄ͕͋Δ • 7 Patterns to Refactor Fat ActiveRecord Models • ΦϒδΣΫτࢦ޲ઃܭ࣮ફΨΠυ • RubyʹΑΔσβΠϯύλʔϯ

Slide 12

Slide 12 text

ςετίʔυ ςετίʔυ΋ΞϓϦέʔγϣϯίʔυͱಉ͘͡୯ͳΔRubyͷί ʔυͳͷͰ͕͢ɺଟগؾΛ͚ͭͳ͍ͱμϝͳϙΠϯτ͕͋Δ ͔͠͠ॻ੶΍ωοτهࣄͰ͸͋·Γ৮ΕΒΕ͍ͯͳ͍

Slide 13

Slide 13 text

ςετίʔυ͸௕͍

Slide 14

Slide 14 text

ςετίʔυ(RSpec)͸ ֊૚ߏ଄ʹͳ͍ͬͯΔ

Slide 15

Slide 15 text

ςετίʔυ͸ந৅Խ͠ ͮΒ͍

Slide 16

Slide 16 text

͡Ό͋Ͳ͏ॻ͘ͱ͍͍ͷ ͔

Slide 17

Slide 17 text

લఏ • RSpec • FactoryGirl

Slide 18

Slide 18 text

1.͍ͨͩ͠֊૚ߏ଄Λ࡞ Δ

Slide 19

Slide 19 text

describeͱcontextΛਖ਼͘͠࢖͍෼͚Δ describeͱcontext͸ಉ͡ϝιου͕ͩɺ࣍ͷΑ͏ʹ࢖͍෼͚Δ ͜ͱͰԿΛςετ͍ͯ͠Δͷ͔ΛΘ͔Γ΍͘͢Ͱ͖Δɻ • describeͷҾ਺ʹ͸ςετͷର৅Λॻ͘ • contextͷҾ਺ʹ͸ςετΛ࣮ߦ͢ΔͨΊͷ৚݅Λॻ͘ ※describe, context, it ͷ֤Ҿ਺ʹ͖ͪΜͱͨ͠આ໌Λॻ͘ ͷ΋௒େࣄ!!!

Slide 20

Slide 20 text

# ྫ describe Stack do let!(:stack) { Stack.new } describe '#push' do context 'จࣈྻΛpushͨ͠ͱ͖' do before { stack.push('value') } it 'ฦΓ஋͕pushͨ͠஋Ͱ͋Δ͜ͱ' do expect(stack).to eq 'value' end end context 'nilΛpushͨ͠৔߹' do it 'ArgumentErrorʹͳΔ͜ͱ' do expect { stack.push(nil) }.to raise_error(ArgumentError) end end end describe '#pop' do # ུ end end

Slide 21

Slide 21 text

describeͷ֎ʹςετσʔλΛஔ͔ͳ͍ σʔλͷείʔϓΛߟྀͯ͠ɺςετέʔεʹෆཁͳσʔλ͕ଘࡏ ͢Δ͜ͱΛආ͚Δ

Slide 22

Slide 22 text

# ҰݟDRYʹͰ͖ͦ͏ʹݟ͑Δ describe 'sample specs' do context 'a' do # ... end context 'b' do let!(:need_in_b_and_c) { ... } # ... end context 'c' do let!(:need_in_b_and_c) { ... } # ... end end

Slide 23

Slide 23 text

# Ͱ΋͜͏มߋͯ͠͠·͏ͷ͸NG # ͦΕͧΕͷcontextͰඞཁͳσʔλͱඞཁͳ͍σʔλΛ೴಺ͰબΓ෼͚Δඞཁ͕ग़ͯ͘Δ describe 'sample specs' do let!(:need_in_b_and_c) { ... } context 'a' do # ... end context 'b' do # ... end context 'c' do # ... end end

Slide 24

Slide 24 text

ྫ͚ͩͩͱʮผʹରͯ͠มΘΒͳ͘ͳ͍ʁʯͬͯࢥ͏͔΋͠Εͳ͍ ͚Ͳɺ࣮ࡍͷςετίʔυ͸let΍context͕΋ͬͱେྔʹ͋ͬͯ Θ͚Θ͔Βͳ͍ͷͰɺෆཁͳσʔλ͕ͳ͍ঢ়ଶΛอ࣋͢ΔͷϚδେ ࣄͳΜͰ͢Α

Slide 25

Slide 25 text

2. ґଘΛݮΒ͢

Slide 26

Slide 26 text

FactoryGirlͷσϑΥϧτ஋ʹґଘͤ͞ͳ͍ FactoryGirlͷσϑΥϧτ஋͸͢΂ͯϥϯμϜ஋ʹ͢Δɻ ඞཁͳ஋ͷΈΛςετதͰ໌ࣔతʹࢦఆ͢Δ͜ͱʹΑΓɺʮ͜ͷς ετͰॏཁͳ஋͸ͳʹ͔ʯ͕Θ͔Γ΍͘͢ͳΔɻ

Slide 27

Slide 27 text

ྑ͘ͳ͍ྫ FactoryGirl.define do factory :user do name 'willnet' active true end end

Slide 28

Slide 28 text

describe User, type: :model do describe '#send_message' do let!(:sender) { create :user, name: 'maeshima' } let!(:receiver) { create :user, name: 'kamiya' } it 'ϝοηʔδ͕ਖ਼͘͠ૹΒΕΔ͜ͱ' do expect { sender.send_message(receiver: receiver, body: 'hello!') } .to change { Message.count }.by(1) end end end

Slide 29

Slide 29 text

্༙͖͕Δٙ໰ • User#active #=> falseͷͱ͖͸Ͳ͏ͳΔʁ • User#nameͬͯઃఆ͢Δඞཁ͋Δͷʁ

Slide 30

Slide 30 text

Α͍ྫ FactoryGirl.define do factory :user do sequence(:name) { |i| "test#{i}"} active { [true, false].sample } end end

Slide 31

Slide 31 text

describe User, type: :model do describe '#send_message' do let!(:sender) { create :user } let!(:receiver) { create :user } it 'ϝοηʔδ͕ਖ਼͘͠ૹΒΕΔ͜ͱ' do expect { sender.send_message(receiver: receiver, body: 'hello!') } .to change { Message.count }.by(1) end end end

Slide 32

Slide 32 text

͜ΕͰ(ςετର৅ͷίʔυΛಡ·ͳͯ͘΋)࣍ͷ͜ͱ͕Θ͔Δ • User#name͕User#send_messageͷಈ࡞ʹӨڹ͠ͳ͍ • User#active͕User#send_messageͷಈ࡞ʹӨڹ͠ͳ͍ σϑΥϧτ஋͕มߋ͞ΕͨΒςετ͕ίέΔΈ͍ͨͳ͜ͱ΋ͳ͘ͳ Δ

Slide 33

Slide 33 text

FactoryGirlͰbelongs_toҎ֎ͷؔ࿈Λσϑ ΥϧτͰ࡞੒͠ͳ͍ has_manyͳͲͷؔ࿈ઌΛσϑΥϧτͷఆٛͰੜ੒ͯ͠͠·͏ͱڧ ྽ͳґଘؔ܎ʹͳͬͯ͠·͏ͷͰආ͚Δ

Slide 34

Slide 34 text

ѱ͍ྫ FactoryGirl.define do factory :user do sequence(:name) { |i| "username#{i}" } after(:create) do |user, evaluator| create_list(:post, 2, user: user) end end end

Slide 35

Slide 35 text

RSpec.describe User, type: :model do describe '#posts_ordered_by_popularity' do let!(:user) { create(:user) } let!(:post_popular) do post = user.posts[0] post.update(popularity: 5) post end let!(:post_not_popular) do post = user.posts[1] post.update(popularity: 1) post end it 'return posts ordered by populality' do expect(user.posts_ordered_by_popularity).to eq [post_popular, post_not_popular] end end end

Slide 36

Slide 36 text

• after(:create)Ͱؔ࿈ઌΛੜ੒͍ͯ͠ΔͨΊɺؔ࿈ઌͷݸ਺ ΍ଐੑ͕ઃఆͰ͖ͳ͍ • ࠷ऴతʹͲΜͳσʔλ͕ἧ͍ͬͯΔͷ͔େมΘ͔Γʹ͍͘

Slide 37

Slide 37 text

ྑ͍ྫ FactoryGirl.define do factory :user do sequence(:name) { |i| "username#{i}" } trait(:with_posts) do after(:create) do |user, evaluator| create_list(:post, 2, user: user) end end end end

Slide 38

Slide 38 text

RSpec.describe User, type: :model do describe '#posts_ordered_by_popularity' do let!(:user) { create(:user) } let!(:post_popular) { create :post, user: user, popularity: 5 } let!(:post_not_popular) { create :post, user: user, popularity: 1 } it 'return posts ordered by populality' do expect(user.posts_ordered_by_popularity).to eq [post_popular, post_not_popular] end end end

Slide 39

Slide 39 text

೔࣌ʹ(ͳΔ΂͘)ґଘͤ͞ͳ͍ ೔࣌ΛऔΓѻ͏ςετΛॻ͘৔߹ɺઈର࣌ؒΛ࢖͏ඞཁ͕ͳ͍έʔ εͰ͋Ε͹ͳΔ΂͘ݱࡏ೔͔࣌Βͷ૬ର࣌ؒΛར༻͢Δͷ͕ྑ͍ɻ ͦͷํ͕࣮૷ͷෆ۩߹ʹؾ͚ͮΔՄೳੑ͕૿͢ɻ

Slide 40

Slide 40 text

# ѱ͍ྫ class Post < ApplicationRecord # όάؚ͕·Ε͍ͯΔ scope :last_month_published, -> { where(publish_at: (Time.zone.now - 31.days).all_month) } end RSpec.describe Post, type: :model do describe '.last_month_published' do let!(:april_1st) { create :post, publish_at: Time.zone.local(2017, 4, 1) } let!(:april_30th) { create :post, publish_at: Time.zone.local(2017, 4, 30) } before do create :post, publish_at: Time.zone.local(2017, 5, 1) create :post, publish_at: Time.zone.local(2017, 3, 31) end it 'return published posts in last month' do Timecop.travel(2017, 5, 6) do # ςετ͸௨Δ expect(Post.last_month_published).to contain_exactly(april_1st, april_30th) end end end end

Slide 41

Slide 41 text

# ྑ͍ྫ # ઈରͰ͸ͳ͍͕ෆ۩߹ʹؾͮ͘Մೳੑ͕૿͢ RSpec.describe Post, type: :model do describe '.last_month_published' do let!(:now) { Time.zone.now } let!(:last_beginning_of_month) { create :post, publish_at: 1.month.ago(now).beginning_of_month } let!(:last_end_of_month) { create :post, publish_at: 1.month.ago(now).end_of_month } before do create :post, publish_at: now create :post, publish_at: 2.months.ago(now) end it 'return published posts in last month' do expect(Post.last_month_published).to contain_exactly(last_beginning_of_month, last_end_of_month) end end end

Slide 42

Slide 42 text

3. લఏ৚݅ΛΘ͔Γ΍ ͘͢͢Δ

Slide 43

Slide 43 text

updateͰσʔλΛมߋ͠ͳ͍ FactoryGirlͰ࡞੒ͨ͠ϨίʔυதͷΧϥϜΛupdateϝιου Ͱมߋ͢Δͱɺ࠷ऴతͳϨίʔυͷঢ়ଶ͕Θ͔Γʹ͘͘ͳΔ͠ɺς ετʹґଘ͍ͯ͠Δଐੑ΋Θ͔Γʹ͘͘ͳΔͷͰආ͚Δɻ

Slide 44

Slide 44 text

# ѱ͍ྫ describe Post do let!(:post) { create :post } describe '#published?' do subject { post.published? } context 'when the post has already published' do it { is_expected.to eq true } end context 'when the post has not published' do before { post.update(publish_at: nil) } it { is_expected.to eq false } end context 'when the post is closed' do before { post.update(status: :close) } it { is_expected.to eq false } end context 'when the title includes "[WIP]"' do before { post.update(title: '[WIP]hello world') } it { is_expected.to eq false } end end end

Slide 45

Slide 45 text

letΛ্ॻ͖͠ͳ͍ ઌఔͷupdateͷ࿩ͱಉ͡ɻ֤ςετͷલఏ৚͕݅Θ͔ΓͮΒ͘ͳ ΔͷͰආ͚Δ

Slide 46

Slide 46 text

# ѱ͍ྫ describe Post do let!(:post) { create :post, title: title, status: status, publish_at: publish_at } let(:title) { 'hello world' } let(:status) { :open } let(:publish_at) { Time.zone.now } describe '#published?' do subject { post.published? } context 'when the post has already published' do it { is_expected.to eq true } end context 'when the post has not published' do let(:publish_at) { nil } it { is_expected.to eq false } end context 'when the post is closed' do let(:status) { :close } it { is_expected.to eq false } end context 'when the title includes "[WIP]"' do let(:title) { '[WIP]hello world'} it { is_expected.to eq false } end end end

Slide 47

Slide 47 text

4. ந৅Խ͢Δͱ͖͸Α ͘ߟ͑ͯ

Slide 48

Slide 48 text

shared_examples͸Α͘ߟ͑ͯ࢖͏ shared_examplesΛར༻͢ΔͱίʔυͷॏෳΛ࡟আͰ͖Δ͕ɺॻ ͖ํʹΑͬͯ͸͔͑ͬͯՄಡੑΛམͱ͢͜ͱ͕͋Δɻ

Slide 49

Slide 49 text

# ѱ͍ྫ # Ҿ਺ͱͯ͠౉͞Ε༵ͨ೔ʹରԠͨ͠෼͚ͩϙΠϯτΛ૿΍͢ϝιου # `Point#increase_by_day_of_the_week`Λςετ͍ͯ͠Δ RSpec.describe Point, type: :model do describe '#increase_by_day_of_the_week' do let(:point) { create :point, point: 0 } it_behaves_like 'point increasing by day of the week', 100 do let(:wday) { 0 } end it_behaves_like 'point increasing by day of the week', 50 do let(:wday) { 1 } end it_behaves_like 'point increasing by day of the week', 30 do let(:wday) { 2 } end # ... end end

Slide 50

Slide 50 text

͜Εݟͯͳʹ͕Ͳ͏ͳͬͯΔͷ͔͙͢ཧղͰ͖·͔ͨ͠ʁ

Slide 51

Slide 51 text

ఆٛ͸ͪ͜Β RSpec.shared_examples 'point increasing by day of the week' do |expected_point| it "increase by #{expected_point}" do expect(point.point).to eq 0 point.increase_by_day_of_the_week(wday) expect(point.point).to eq expected_point end end

Slide 52

Slide 52 text

# ѱ͍ྫ(࠶ܝ) # - લఏ৚͕݅ଟ͍ # - ఆ͕ٛ෼ࢄ͍ͯ͠Δ RSpec.describe Point, type: :model do describe '#increase_by_day_of_the_week' do let(:point) { create :point, point: 0 } it_behaves_like 'point increasing by day of the week', 100 do let(:wday) { 0 } end it_behaves_like 'point increasing by day of the week', 50 do let(:wday) { 1 } end it_behaves_like 'point increasing by day of the week', 30 do let(:wday) { 2 } end # ... end end

Slide 53

Slide 53 text

# shared_examples Λ࢖ͬͨ··ՄಡੑΛଟগ૿ͨ͠ྫ RSpec.describe Point, type: :model do describe '#increase_by_day_of_the_week' do let(:point) { create :point, point: 0 } context 'on sunday' do let(:wday) { 0 } it_behaves_like 'point increasing by day of the week', expected_point: 100 end context 'on monday' do let(:wday) { 1 } it_behaves_like 'point increasing by day of the week', expected_point: 50 end context 'on tuesday' do let(:wday) { 2 } it_behaves_like 'point increasing by day of the week', expected_point: 30 end # ... end end

Slide 54

Slide 54 text

# ϒϩοΫҾ਺ΛύϥϝʔλҾ਺ʹͨ͠ RSpec.shared_examples 'point increasing by day of the week' do |expected_point:| it "increase by #{expected_point}" do expect(point.point).to eq 0 point.increase_by_day_of_the_week(wday) expect(point.point).to eq expected_point end end

Slide 55

Slide 55 text

# shared_examples ΛࣙΊͨྫ RSpec.describe Point, type: :model do describe '#increase_by_day_of_the_week' do let(:point) { create :point, point: 0 } context 'on sunday' do let(:wday) { 0 } it "increase by 100" do expect(point.point).to eq 0 point.increase_by_day_of_the_week(wday) expect(point.point).to eq 100 end end context 'on monday' do let(:wday) { 1 } it "increase by 50" do expect(point.point).to eq 0 point.increase_by_day_of_the_week(wday) expect(point.point).to eq 50 end end # ུ end end

Slide 56

Slide 56 text

it_behaves_like '○○'` ʮ○○ͷΑ͏ʹৼΔ෣͏ʯͰදݱͰ͖ͳ͍Α͏ͳέʔεͰ͸߇͑ͨ ΄͏͕Α͍ͷͰ͸

Slide 57

Slide 57 text

ʮԶͳΒ͜͏ॻ͘ʯΈ͍ͨͳ࿩ืू͠ ͍ͯ·͢

Slide 58

Slide 58 text

ࠓ೔ͷ࿩͸࣍ͷGitHubϨϙδτϦʹ͍͍ͩͨ· ͱΊͯ·͢ ࠓ೔࿩ͨ͠Ҏ֎ͷ߲໨΋͋Γ·͢ https://github.com/willnet/rspec-style-guide

Slide 59

Slide 59 text

ποίϛืू Issue, Pull Request͓·͍ͪͯ͠·͢

Slide 60

Slide 60 text

Happy Testing!