Ruby Testing Micro-Course, Lesson 2

by Jason Swett,

Lesson 1 / Lesson 2 / Lesson 3 / Lesson 4

Review of Lesson 1

At the end of Lesson 1 you were asked to write a test for checking a guest out.

If you haven’t completed Lesson 1, go back and complete it before you proceed, or at least try. Don’t feel bad if you tried and failed to come up with a test in Lesson 1. I’m about to show you my test and how I came up with it. You’ll have more chances soon to make another attempt.

What We’ll Do In Lesson 2

In Lesson 2 I’m going to let you compare your test to mine. Then you’ll have a chance to see how I came up with the test I wrote.

Then we’ll start to add a new feature to our hotel application, room numbers.

Compare Your Test to Mine

Here’s the test (and new application code) I wrote for checking out a guest. The new code is highlighted.

require 'rspec'

class Hotel
  attr_accessor :guests

  def initialize
    @guests = []
  end

  def check_in_guest(guest_name)
    @guests << guest_name
  end

  def check_out_guest(guest_name)
    @guests.delete(guest_name)
  end
end

describe Hotel do
  it 'can check a guest in' do
    hotel = Hotel.new
    hotel.check_in_guest('George Harrison')
    expect(hotel.guests).to include 'George Harrison'
  end

  it 'can check a guest out' do
    hotel = Hotel.new
    hotel.check_in_guest('Buddy Holly')
    hotel.check_out_guest('Buddy Holly')
    expect(hotel.guests).not_to include 'Buddy Holly'
  end
end

Now let me show you the steps I took to come up with this test.

How I Got Here

Before I wrote any code I asked myself, “How would I verify that a check-out feature is working?”

My answer was that I would check a guest in, then check that same guest back out. The hotel should no longer include that guest in its guest list.

Then I translated this sequence of actions—check a guest in, check a guest out, check for that guest—into code. Below is what I added.

require 'rspec'

class Hotel
  attr_accessor :guests

  def initialize
    @guests = []
  end

  def check_in_guest(guest_name)
    guests << guest_name
  end
end

describe Hotel do
  it 'can check a guest in' do
    hotel = Hotel.new
    hotel.check_in_guest('George Harrison')
    expect(hotel.guests).to include 'George Harrison'
  end

  it 'can check a guest out' do
    hotel = Hotel.new
    hotel.check_in_guest('Buddy Holly')
    hotel.check_out_guest('Buddy Holly')
    expect(hotel.guests).not_to include 'Buddy Holly'
  end
end

Then I ran the test, knowing it wouldn’t work. Here’s what I got.

$ rspec hotelier_spec.rb
.F

Failures:

  1) Hotel can check a guest out
     Failure/Error: hotel.check_out_guest('Buddy Holly')
     
     NoMethodError:
       undefined method `check_out_guest' for #<Hotel:0x007f8b3e021d60 @guests=["Buddy Holly"]>
       Did you mean?  check_in_guest
     # ./hotelier_spec.rb:25:in `block (2 levels) in <top (required)>'

Finished in 0.00649 seconds (files took 0.13307 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./hotelier_spec.rb:22 # Hotel can check a guest out

The test of course didn’t work. The test didn’t even fail, it errored. My next step was to simply get the test to stop erroring.

require 'rspec'

class Hotel
  attr_accessor :guests

  def initialize
    @guests = []
  end

  def check_in_guest(guest_name)
    @guests << guest_name
  end

  def check_out_guest(guest_name)
  end
end

describe Hotel do
  it 'can check a guest in' do
    hotel = Hotel.new
    hotel.check_in_guest('George Harrison')
    expect(hotel.guests).to include 'George Harrison'
  end

  it 'can check a guest out' do
    hotel = Hotel.new
    hotel.check_in_guest('Buddy Holly')
    hotel.check_out_guest('Buddy Holly')
    expect(hotel.guests).not_to include 'Buddy Holly'
  end
end

Here’s what this test run looks like:

$ rspec hotelier_spec.rb
.F

Failures:

  1) Hotel can check a guest out
     Failure/Error: expect(hotel.guests).not_to include 'Buddy Holly'
       expected ["Buddy Holly"] not to include "Buddy Holly"
     # ./hotelier_spec.rb:29:in `block (2 levels) in <top (required)>'

Finished in 0.02465 seconds (files took 0.0844 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./hotelier_spec.rb:25 # Hotel can check a guest out

The test no longer errors out. It just fails.

If we were to translate this test failure to English it might be something like “I expected the list of guests not to include Buddy Holly, but it did include Buddy Holly”.

Now all that’s left to do is make the test pass.

require 'rspec'

class Hotel
  attr_accessor :guests

  def initialize
    @guests = []
  end

  def check_in_guest(guest_name)
    @guests << guest_name
  end

  def check_out_guest(guest_name)
    @guests.delete(guest_name)
  end
end

describe Hotel do
  it 'can check a guest in' do
    hotel = Hotel.new
    hotel.check_in_guest('George Harrison')
    expect(hotel.guests).to include 'George Harrison'
  end

  it 'can check a guest out' do
    hotel = Hotel.new
    hotel.check_in_guest('Buddy Holly')
    hotel.check_out_guest('Buddy Holly')
    expect(hotel.guests).not_to include 'Buddy Holly'
  end
end

Now the test will pass.

$ rspec hotelier_spec.rb
..

Finished in 0.00467 seconds (files took 0.0835 seconds to load)
2 examples, 0 failures

What’s Next

So far you’ve gotten a chance to try writing your own test. Then you watched me write a test for the same feature.

Next I’m going to let you watch me develop a new feature and write tests at the same time. Then I’ll give you a chance to try writing another test of your own.

Room Numbers

In a real hotel you don’t just check into the hotel generally. You of course check into a specific room.

Let’s add some room functionality to our application.

I’m going to begin adding room functionality using coding by wishful thinking. In other words, I’ll ask myself, “What code do I wish I could use to check in a guest?” Below is what I came up with.

require 'rspec'

class Hotel
  attr_accessor :guests

  def initialize
    @guests = []
  end

  def check_in_guest(guest_name)
    @guests << guest_name
  end

  def check_out_guest(guest_name)
    @guests.delete(guest_name)
  end
end

describe Hotel do
  it 'can check a guest in' do
    hotel = Hotel.new
    hotel.check_in_guest('George Harrison', 302)
    expect(hotel.guests).to include 'George Harrison'
  end

  it 'can check a guest out' do
    hotel = Hotel.new
    hotel.check_in_guest('Buddy Holly')
    hotel.check_out_guest('Buddy Holly')
    expect(hotel.guests).not_to include 'Buddy Holly'
  end
end

The `check_in_guest` method currently doesn’t take a second argument, so one of my tests will fail.

$ rspec hotelier_spec.rb
F.

Failures:

  1) Hotel can check a guest in
     Failure/Error:
       def check_in_guest(guest_name)
         @guests << guest_name
       end
     
     ArgumentError:
       wrong number of arguments (given 2, expected 1)
     # ./hotelier_spec.rb:10:in `check_in_guest'
     # ./hotelier_spec.rb:22:in `block (2 levels) in <top (required)>'

Finished in 0.00386 seconds (files took 0.08159 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./hotelier_spec.rb:20 # Hotel can check a guest in

The way to make this test pass of course is to make the `check_in_guest` method take another argument. I’ll also have to change my other test’s use of `check_in_guest` to specify a room number.

require 'rspec'

class Hotel
  attr_accessor :guests

  def initialize
    @guests = []
  end

  def check_in_guest(guest_name, room_number)
    @guests << guest_name
  end

  def check_out_guest(guest_name)
    @guests.delete(guest_name)
  end
end

describe Hotel do
  it 'can check a guest in' do
    hotel = Hotel.new
    hotel.check_in_guest('George Harrison', 302)
    expect(hotel.guests).to include 'George Harrison'
  end

  it 'can check a guest out' do
    hotel = Hotel.new
    hotel.check_in_guest('Buddy Holly', 303)
    hotel.check_out_guest('Buddy Holly')
    expect(hotel.guests).not_to include 'Buddy Holly'
  end
end

This new specifying-a-room-number thing technically works in the sense that the tests pass, but it’s a little unsatisfying. As of right now, specifying a room doesn’t actually do anything.

It seems like checking a guest into a room should do at least three things:

  1. Add the guest to the hotel’s guest list (we’re already testing for this)
  2. Disallow another guest from checking into that same room
  3. Decrease the total number of available rooms

We’ll address these things in Lesson 3.

Continue to Lesson 3 >>>

2 thoughts on “Ruby Testing Micro-Course, Lesson 2

  1. BV

    Hey Jason – just finished watching the first two lessons and feel this micro course will be a great learning resource for developers new to ruby testing via RSpec. I find the examples and explanations relevant and easy to follow. I’m looking forward to following future lessons so please keep going with the great work.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *