5 years know-how of RSpec driven Rails app. development MOROHASHI Kyosuke (@moro)
Jan 19, 2015
5 years know-how of
RSpec drivenRails app. development
MOROHASHI Kyosuke(@moro)
Kyosuke MOROHASHI
✓ http://twitter.com/moro
✓ http://github.com/moro
✓ Eiwa System Management, Inc.
http://tatsu-zine.com/books/cuke
RubyKaigi
special price
40% off
http://amzn.to/rails3recipebook
Available
@Junku-Do
RubyKaigi
http://www.junkudo.co.jp/tenpo/evtalk.html#20110721ikebukuro
5 years know-how of
RSpec drivenRails app. development
MOROHASHI Kyosuke(@moro)
諸橋 恭介
3の技
レシピブック
Test Context Arrangement
✓What test should I write first?
✓ Write model unit test.✓I began model test,but it’s difficult to setup data!!
✓ Use multi strategies for multi types of data.
✓It seems good but less DRY. Is there any idea?
✓ Share your testing context.
I’ll talk about.
Probl
em
What test should
I write first?
Solu
tion
Write Modelunit test.
Skinny ControllersFat Models
People say
#64 2011-07-18 12:00@ねりぶん集会室
http://twitter.com/railstokyo
RubyKaigi
special price
40% off
http://tatsu-zine.com/books/cuke
Use Cucumber(or other end-to-end testing)
to cover controller, views and more.
Model unit test
Probl
em
Began model test, but it’s difficult to setup data!!
events
courses
rooms periods
registrations
students
lessons
Course-1 Course-2
Period-1
Period-2
Period-3
Period-4
Period-5
Lesson-1-1 Lesson-2-1
Lesson-1-2 Lesson-2-2
Lesson-1-3 Lesson-2-3
Lesson-1-4 Lesson-2-4
Lesson-1-5 Lesson-2-5
registration
Course-1 Course-2
Period-1
Period-2
Period-3
Period-4
Period-5
Lesson-1-1 Lesson-2-1
Lesson-1-2 Lesson-2-2
Lesson-1-3 Lesson-2-3
Lesson-1-4 Lesson-2-4
Lesson-1-5 Lesson-2-5
registration
Student#subscribe(course_1)
Course-1 Course-2
Period-1
Period-2
Period-3
Period-4
Period-5
Lesson-1-1 Lesson-2-1
Lesson-1-2 Lesson-2-2
Lesson-1-3 Lesson-2-3
Lesson-1-4 Lesson-2-4
Lesson-1-5 Lesson-2-5
registration
Student#register!(lesson_2_4)
student.subscribe(course)
student.register!( @another_course.lessons[3])
describe Student do let(:student) { Student.create(name: 'Alice') } before do event = Event.create(name: 'Camp') course = event.courses.create(name: 'Course-1') 5.times {¦i¦ course.lessons.create(name: "Lessson-#{i}") } student.subscribe(course)
@another_course = event.courses.create(name: 'Course-2') 5.times {¦i¦ @another_course.lessons.create(name: "NewLessson-#{i}") } student.register!(@another_course.lessons[3]) end its(:registering_lessons) do should include(@another_course.lessons[3]) endend
describe Student do let(:student) { Student.create(name: 'Alice') } before do event = Event.create(name: 'Camp') course = event.courses.create(name: 'Course-1') 5.times {¦i¦ course.lessons.create(name: "Lessson-#{i}") } student.subscribe(course)
@another_course = event.courses.create(name: 'Course-2') 5.times {¦i¦ @another_course.lessons.create(name: "NewLessson-#{i}") } student.register!(@another_course.lessons[3]) end its(:registering_lessons) do should include(@another_course.lessons[3]) endend
Solu
tion
Use multi strategies for multi types of data.
✓ 3 types of data✓ 3 strategies
✓ 3 types of data✓ 3 strategies
✓Master data✓ Resource data✓ Event data
✓ Created/updated rarely.✓e.g. rooms / periods.✓ Less dependencies to others.✓ Maintained by scaffold or so.
Mastar data
✓Mainly created/updated data.
✓ Users themselves maintains this type of data.
Resource data
✓ Represent relationship between resources.
✓ Used for has_many :through ‘s through table in Rails.
Event data
✓ 3 types of data✓ 3 strategies
✓ Fixture
✓ Fixture replacement
✓ before/setup block
✓ good speed to be loaded.
✓ less maintainability.✓ less flexibility.
✓ flexible.✓ powerful but
difficult to use correctly.
✓ slow to be loaded.
Fabricator(:event) do name 'SummerCamp' courses!(count: 2) {¦e, i¦ Fabricate(:course, event: e) }end
Fabricator(:course) do name { sequence(:course_name) {¦i¦ "Course-#{i+1}" } } lessons!(count: 5) {¦course, i¦ Fabricate(:lesson, course: course, period_id: i) }end
Fabricator(:lesson) do name { sequence(:lesson_name) {¦i¦ "Lesson-#{i+1}" } } period_id 1 room_id 1end
Which should we use rails-fixture or fixture replacement?
Use them both which suitable for the data.
describe Student do let(:student) { Student.create(name: 'Alice') }
describe "#register" do
subject { student }
before do student.subscribe(course) student.register(another_course.lessons[3]) end
its(:registering_lessons) do
before/setup block
✓ each run per test.✓ same speed with real data persistence.
✓ edit each of them to maintain.
speed flexibilityin test
maintain-ability good for
fixture best wrong wrongMaster
data
fixturereplacements wrong good best
Resourcedata
before() good best
goodfor eachwrongfor all
Eventdata
events
courses lessons
rooms periods
registrations
students
rooms periods
Master
events
courses lessons
studentsResource
registrations
Event
describe Student do # fixture :all let(:student) { Fabricate(:student) }
describe "#register" do let(:event) { Fabricate(:event) } let(:course) { event.courses.first } let(:another_course) { event.courses.last }
subject { student }
before do student.subscribe(course) student.register!(another_course.lessons[3]) end
its(:registering_lessons) do should include(another_course.lessons[3]) end
Solu
tion
Use multi strategies for multi types of data.
type, strategy = if user.rarely_maitain?(data) [MastarData, :fixture] elsif data.through_table? [EventData, :before] else [ResourceData, :fixture_replacement] end
✓ fixture✓ fixture replacement✓ before/setup block
Use them all which suitable for the data.
Probl
em
It seems good but less DRY.Is there any idea?
Course Students Count
Period-1
Period-2
Period-3
Period-4
Period-5
Lesson-1-1 2
Lesson-1-2 3
Lesson-1-3 3
Lesson-1-4 3
Lesson-1-5 2
Course#attendee_count
describe Course do describe '#attendee_per_lesson' do let(:student) { Fabricate(:student) } let(:event) { Fabricate(:event) } let(:course) { event.courses.first }
subject { course }
before do student.subscribe(course) Student.create(name: 'Bob').subscribe(course) end
its('attendee_count.first') do should == [course.lessons[0], 2] end
describe Course do describe '#attendee_per_lesson' do let(:student) { Fabricate(:student) } let(:event) { Fabricate(:event) } let(:course) { event.courses.first }
subject { course }
before do student.subscribe(course) Student.create(name: 'Bob').subscribe(course) end
its('attendee_count.first') do should == [course.lessons[0], 2] end
describe Student do # fixture :all let(:student) { Fabricate(:student) }
describe "#register!" do let(:event) { Fabricate(:event) } let(:course) { event.courses.first } let(:another_course) { event.courses.last }
subject { student }
before do student.subscribe(course) student.register!(another_course.lessons[3]) end
its(:registering_lessons) do should include(another_course.lessons[3]) end
Solu
tion
Share your testing context.
✓naming each✓naming them
✓naming each✓naming them
“Generates a method whose return value is memoized after the first call.
describe 'TheAnswer' do let(:answer) { 42 } specify { answer.should == 42 }end
✓ name each focusing object.
✓ Build strategy doesn’t matter.
✓naming each✓naming them
✓ RSpec’s shared_context()
✓let()
✓before()
shared_context "A student subscribes a course" do let(:student) { Fabricate(:student) } let(:event) { Fabricate(:event) } let(:course) { event.courses.first }
before do student.subscribe(course) endend
event
course-1
course-2
Alice<student>
registrationsregistrationsregistrationsregistrations
room-1 period-1room-1room-1room-1room-1period-1period-1period-1period-1
lesson-1-1lesson-1-1lesson-1-1lesson-1-1lesson-1-1
lesson-2-1lesson-2-1lesson-2-1lesson-2-1lesson-2-1
A student subscribes a course
require 'spec_helper'require 'student_subscribe_a_course'
describe Student do include_context "A student subscribes a course"
describe "#register!" do let(:another_course) { event.courses.last } subject { student }
before do student.register!(another_course.lessons[3]) end
its(:registering_lessons) do should include(another_course.lessons[3]) end endend
require 'spec_helper'require 'student_subscribe_a_course'
describe Course do include_context "A student subscribes a course"
describe '#attendee_per_lesson' do
subject { course }
before do Student.create(name: 'Bob').subscribe(course) end
its('attendee_count.first') do should == [course.lessons[0], 2] end endend
Express your intention clearly and reduce tiredness to add new test.
✓ naming each w/ let()✓ naming them w/
shared_context()
Solu
tion
Share your testing context.
Conclusion
✓What test should I write first?
✓ Write model unit test.✓I began model test,but it’s difficult to setup data!!
✓ Use multi strategies for multi types of data.
✓It seems good but less DRY. Is there any idea?
✓ Share your testing context.
I’ve talked about.
✓ fixture✓ fixture replacement✓ before/setup block
Use them all which suitable for the data.
Express your intention clearly and reduce tiredness to add new test.
✓ naming each w/ let()✓ naming them w/
shared_context()
May a testbe with you.
May a testbe with you.