Slide 1

Slide 1 text

“Safe testing” @sebasporto Thursday, 26 September 13

Slide 2

Slide 2 text

user let(:user) { User.new } it “tests something” do expect(user).to be_something end Thursday, 26 September 13

Slide 3

Slide 3 text

Things get complicated user language location roles permissions Thursday, 26 September 13

Slide 4

Slide 4 text

let(:location) { Location.new } ... let(:user) { User.new(location: location, language: language) } We can create things manually Or use factories let(:user) { build(:user) } Thursday, 26 September 13

Slide 5

Slide 5 text

Problem 1: A whole universe to test one thing Thursday, 26 September 13

Slide 6

Slide 6 text

it ‘increase the watch count’ { user.watch(video) expect(user.watch_count).to } Problem 2: Too much happening class User def watch(video) ... video.watched_by(self) end end class Video def watched_by(user) ... end end Thursday, 26 September 13

Slide 7

Slide 7 text

SLOW!!! Thursday, 26 September 13

Slide 8

Slide 8 text

Doubles! Stubs! Thursday, 26 September 13

Slide 9

Slide 9 text

class User def watch(video) ... video.watched_by(self) end end Much faster!! No interaction with video anymore let(:video) { double.as_null_object } it { user.watch(video) expect(user.watch_count).to } Thursday, 26 September 13

Slide 10

Slide 10 text

let(:video) { double.as_null_object } it { user.watch(video) expect(user.watch_count).to } But it may break class User def watch(video) ... video.watched_by(self) end end class Video def seen_by(user) ... end end Test still passes! Thursday, 26 September 13

Slide 11

Slide 11 text

Not to worry. Integration test will save you! Thursday, 26 September 13

Slide 12

Slide 12 text

Integration breaks Fix the code in B Unit test for B breaks Fix unit test for B Change code in A Thursday, 26 September 13

Slide 13

Slide 13 text

Integration test coverage? Will they? Thursday, 26 September 13

Slide 14

Slide 14 text

Your test Thursday, 26 September 13

Slide 15

Slide 15 text

Your app Thursday, 26 September 13

Slide 16

Slide 16 text

Can we get benefits without the drawbacks? Thursday, 26 September 13

Slide 17

Slide 17 text

Safer testing Rspec fire Quacky Bogus ... Thursday, 26 September 13

Slide 18

Slide 18 text

Bogus Safe Fakes Safe Stubbing Safe Mocks Thursday, 26 September 13

Slide 19

Slide 19 text

Fakes Thursday, 26 September 13

Slide 20

Slide 20 text

class User def watch(video) ... video.watched_by(self) end end Rspec doubles let(:video) { double.as_null_object } it { user.watch(video) ... 1 example, 0 failures still passes class Video def seen_by(user) ... end end Thursday, 26 September 13

Slide 21

Slide 21 text

class User def watch(video) ... video.watched_by(self) end end undefined method `watched_by' it breaks Bogus Fakes class Video def seen_by(person) ... end end fake(:video) { Video } it { user.watch(video) ... Thursday, 26 September 13

Slide 22

Slide 22 text

wrong number of arguments (2 for 1) it breaks Fakes (args) class User def watch(video) ... video.watched_by(self, args) end end class Video def watched_by(user) ... end end fake(:video) { Video } it { user.watch(video) ... Thursday, 26 September 13

Slide 23

Slide 23 text

Stubbing Thursday, 26 September 13

Slide 24

Slide 24 text

Rspec Stubbing let(:video) { double.as_null_object } before do video .stub(:time) .and_return(14.minutes) end it { user.watch(video) expect(user.busy_for).to eq(14.minutes) 1 example, 0 failures passes even if video.time doesn’t exist class User ... def busy_for current_video.time end ... end class Video def duration ... end Thursday, 26 September 13

Slide 25

Slide 25 text

Bogus Stubbing fake(:video) { Video } before do stub(video) .time{ 14.minutes } end it { user.watch(video) expect(user.busy_for).to eq(14.minutes) } does not respond to time This is what we want class User ... def busy_for current_video.time end ... end class Video def duration ... end Thursday, 26 September 13

Slide 26

Slide 26 text

Stubbing (args) wrong number of arguments (0 for 1) fake(:video) { Video } before do stub(video) .time(any_args){ 14.minutes } end it { user.watch(video) expect(user.busy_for).to eq(14.minutes) } class User ... def busy_for current_video.time end ... end class Video def time(args) ... end Thursday, 26 September 13

Slide 27

Slide 27 text

Expectations (Mocking) Thursday, 26 September 13

Slide 28

Slide 28 text

Rspec Mocking let(:video) { double.as_null_object } it { video.stub(:watched_by) user.watch(video) expect(video).to have_received(watched_by) } 1 example, 0 failures class User ... def watch(video) ... video.watched_by(self) end ... class Video def seen_by(person) ... end end Thursday, 26 September 13

Slide 29

Slide 29 text

Bogus Mocks let(:video) { double.as_null_object } it { mock(video).watched_by(any_args) user.watch(video) } does not respond to watched_by class User ... def watch(video) ... video.watched_by(self) end ... class Video def seen_by(person) ... end end Thursday, 26 September 13

Slide 30

Slide 30 text

Bogus gem https://www.relishapp.com/bogus Thursday, 26 September 13

Slide 31

Slide 31 text

Thanks @sebasporto Thursday, 26 September 13