Learning TDD With RSpec Part 2

Checkout Kata Part 2

This is part 2 of a planned 3 part series

Introduction:

In part 1, this was achieved;

  1. Initialising project
  2. Writing a test for when nothing has been scanned

In part 2, this is going to be handled;

  1. Scanning of A
  2. Scanning of B
  3. Scanning of C
  4. Scanning of D

Tests will be run using;

  1. ⎵ + t : run entire test ( means the keyboard space bar button)
  2. ⎵ + l : run last test
  3. ⎵ + s: run nearest test

Scanning of A:

Add another context to check for when A has been scanned. The file changes to this;

class CheckOut
  def initialize(pricing_rules)

  end

  def total
    0
  end
end

RSpec.describe 'checkout' do
  context 'when nothing has been scanned' do
    it 'shows a total of zero' do
      pricing_rules = :Rule
      checkout = CheckOut.new(pricing_rules)
      total = checkout.total

      expect(total).to eq 0
    end
  end
  context 'when A has been scanned' do
    it 'shows the price of A' do

      expect(total).to eq 50
    end
  end
end

Run the test by pressing ⎵ + t. We get this error;

~
./bin/rspec spec/check_out_spec.rb

Randomized with seed 34364
.F

Failures:

  1) checkout when A has been scanned shows the price of A
     Failure/Error: expect(total).to eq 50

     NameError:
       undefined local variable or method `total' for #<RSpec::ExampleGroups::Checkout::WhenAHasBeenScanned "shows the price of A" (./spec/check_out_spec.rb:22)>
     # ./spec/check_out_spec.rb:24:in `block (3 levels) in <top (required)>'

Finished in 0.00256 seconds (files took 0.05539 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:22 # checkout when A has been scanned shows the price of A

Randomized with seed 34364


shell returned 1

Press ENTER or type command to continue

Modify the file to add the lines specifying what total is and define pricing_rules;

class CheckOut
  def initialize(pricing_rules)

  end

  def total
    0
  end
end

RSpec.describe 'checkout' do
  context 'when nothing has been scanned' do
    it 'shows a total of zero' do
      pricing_rules = :Rule
      checkout = CheckOut.new(pricing_rules)
      total = checkout.total

      expect(total).to eq 0
    end
  end
  context 'when A has been scanned' do
    it 'shows the price of A' do
      pricing_rules = :Rule
      checkout = CheckOut.new(pricing_rules)
      total = checkout.total

      expect(total).to eq 50
    end
  end
end

When the test is run, as expected we get the error as shown below;

./bin/rspec spec/check_out_spec.rb

Randomized with seed 5579
.F

Failures:

  1) checkout when A has been scanned shows the price of A
     Failure/Error: expect(total).to eq 50

       expected: 50
            got: 0

       (compared using ==)
     # ./spec/check_out_spec.rb:27:in `block (3 levels) in <top (required)>'

Finished in 0.00758 seconds (files took 0.0523 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:22 # checkout when A has been scanned shows the price of A

Randomized with seed 5579


shell returned 1

Press ENTER or type command to continue

A change needs to be made to the spec to include scanning of Item A. This changes the file to be;

 class CheckOut
   def initialize(pricing_rules)

   end

   def total
     0
   end
 end

 RSpec.describe 'checkout' do
   context 'when nothing has been scanned' do
     it 'shows a total of zero' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)
       total = checkout.total

       expect(total).to eq 0
     end
   end
   context 'when A has been scanned' do
     it 'shows the price of A' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)

       checkout.scan('A')
       total = checkout.total

       expect(total).to eq 50
     end
   end
 end

When checkout.scan('A') is added, we get this error;

./bin/rspec spec/check_out_spec.rb

Randomized with seed 45438
.F

Failures:

  1) checkout when A has been scanned shows the price of A
     Failure/Error: checkout.scan('A')

     NoMethodError:
       undefined method `scan' for #<CheckOut:0x0000000130ca8448>
     # ./spec/check_out_spec.rb:26:in `block (3 levels) in <top (required)>'

Finished in 0.00187 seconds (files took 0.04966 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:22 # checkout when A has been scanned shows the price of A

Randomized with seed 45438


shell returned 1

Press ENTER or type command to continue

Add the method: scan(item) to CheckOut class to fix this error;

class CheckOut
  def initialize(pricing_rules)

  end

  def total
    0
  end

  def scan(item)

  end
end

Now we are back to the previous error when the test is run;

./bin/rspec spec/check_out_spec.rb

Randomized with seed 56370
.F

Failures:

  1) checkout when A has been scanned shows the price of A
     Failure/Error: expect(total).to eq 50

       expected: 50
            got: 0

       (compared using ==)
     # ./spec/check_out_spec.rb:33:in `block (3 levels) in <top (required)>'

Finished in 0.00768 seconds (files took 0.04974 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:26 # checkout when A has been scanned shows the price of A

Randomized with seed 56370


shell returned 1

Press ENTER or type command to continue

To fix this error, we make three changes to the CheckOut class as below;


 class CheckOut
   def initialize(pricing_rules)
     @total = 0
   end

   def total
     @total
   end

   def scan(item)
     @total = 50
   end
 end

Now when the test is run, all the current two tests will pass successfully;

./bin/rspec spec/check_out_spec.rb

Randomized with seed 4857
..

Finished in 0.00201 seconds (files took 0.04948 seconds to load)
2 examples, 0 failures

Randomized with seed 4857


Press ENTER or type command to continue

Scanning of B:

Add another context to check for when B has been scanned. Add the context to the RSpec.describe 'checkout' block.

The file changes to this;

 RSpec.describe 'checkout' do
   context 'when nothing has been scanned' do
     it 'shows a total of zero' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)
       total = checkout.total

       expect(total).to eq 0
     end
   end
   context 'when A has been scanned' do
     it 'shows the price of A' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)

       checkout.scan('A')
       total = checkout.total

       expect(total).to eq 50
     end
   end
   context 'when B has been scanned' do
     it 'shows the price of B' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)

       checkout.scan('B')
       total = checkout.total

       expect(total).to eq 30
     end
   end
 end

When ⎵ + s is pressed on the keyboard to only run this new test, the following error is displayed;

./bin/rspec spec/check_out_spec.rb:36
Run options: include {:locations=>{"./spec/check_out_spec.rb"=>[36]}}

Randomized with seed 53332
F

Failures:

  1) checkout when B has been scanned shows the price of B
     Failure/Error: expect(total).to eq 30

       expected: 30
            got: 50

       (compared using ==)
     # ./spec/check_out_spec.rb:44:in `block (3 levels) in <top (required)>'

Finished in 0.00643 seconds (files took 0.0565 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:37 # checkout when B has been scanned shows the price of A

Randomized with seed 53332


shell returned 1

Press ENTER or type command to continue

To fix this, make changes to the scan method in the CheckOut class;

class CheckOut
  def initialize(pricing_rules)
    @total = 0
  end

  def total
    @total
  end

  def scan(item)
    if item == 'A'
      @total = 50
    elsif item == 'B'
      @total = 30
    end
  end
end

Now when press ⎵ + l to run the last test, the test for scanning B now passes as well;

./bin/rspec spec/check_out_spec.rb:40
Run options: include {:locations=>{"./spec/check_out_spec.rb"=>[40]}}

Randomized with seed 59538
.

Finished in 0.00059 seconds (files took 0.05165 seconds to load)
1 example, 0 failures

Randomized with seed 59538


Press ENTER or type command to continue

Scanning of C:

Add another context to check for when C has been scanned. Add the context to the RSpec.describe 'checkout' block.

This is the context;

   context 'when C has been scanned' do
     it 'shows the price of C' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)

       checkout.scan('C')
       total = checkout.total

       expect(total).to eq 20
     end
   end

When + s is pressed to run this new test, this error results;

./bin/rspec spec/check_out_spec.rb:59
Run options: include {:locations=>{"./spec/check_out_spec.rb"=>[59]}}

Randomized with seed 51710
F

Failures:

  1) checkout when C has been scanned shows the price of C
     Failure/Error: expect(total).to eq 20

       expected: 20
            got: 0

       (compared using ==)
     # ./spec/check_out_spec.rb:59:in `block (3 levels) in <top (required)>'

Finished in 0.00602 seconds (files took 0.05125 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:52 # checkout when C has been scanned shows the price of C

Randomized with seed 51710


shell returned 1

Press ENTER or type command to continue

To fix this, make changes to the CheckOut class.

Do a check for item C like so;

 class CheckOut
   def initialize(pricing_rules)
     @total = 0
   end

   def total
     @total
   end

   def scan(item)
     if item == 'A'
       @total = 50
     elsif item == 'B'
       @total = 30
     elsif item == 'C'
       @total = 20
     end
   end
 end

And now when the test is run, it should be pass;

./bin/rspec spec/check_out_spec.rb:59
Run options: include {:locations=>{"./spec/check_out_spec.rb"=>[59]}}

Randomized with seed 2878
.

Finished in 0.00068 seconds (files took 0.05424 seconds to load)
1 example, 0 failures

Randomized with seed 2878


Press ENTER or type command to continue

Scanning of D:

Add another context to check for when D has been scanned. Add the context to the RSpec.describe 'checkout' block.

This is the context;

   context 'when D has been scanned' do
     it 'shows the price of D' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)

       checkout.scan('D')
       total = checkout.total

       expect(total).to eq 15
     end
   end

When + s is pressed to run this new test, this error results;

./bin/rspec spec/check_out_spec.rb:72
Run options: include {:locations=>{"./spec/check_out_spec.rb"=>[72]}}

Randomized with seed 61671
F

Failures:

  1) checkout when D has been scanned shows the price of D
     Failure/Error: expect(total).to eq 15

       expected: 15
            got: 0

       (compared using ==)
     # ./spec/check_out_spec.rb:72:in `block (3 levels) in <top (required)>'

Finished in 0.00622 seconds (files took 0.0561 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:65 # checkout when D has been scanned shows the price of D

Randomized with seed 61671


shell returned 1

Press ENTER or type command to continue

To fix this, make changes to the CheckOut class.

Add an else to check for item D;

 class CheckOut
   def initialize(pricing_rules)
     @total = 0
   end

   def total
     @total
   end

   def scan(item)
     if item == 'A'
       @total = 50
     elsif item == 'B'
       @total = 30
     elsif item == 'C'
       @total = 20
     else 
       @total = 15
     end
   end
 end

And now when the test is run, it should be pass;

./bin/rspec spec/check_out_spec.rb:72
Run options: include {:locations=>{"./spec/check_out_spec.rb"=>[72]}}

Randomized with seed 38120
.

Finished in 0.00059 seconds (files took 0.05152 seconds to load)
1 example, 0 failures

Randomized with seed 38120


Press ENTER or type command to continue

Conclusion:

This concludes part 2.

In part 3, this will handled;

  1. refactoring to make the code better.
  2. taking the pricing rules into account

Shout out to Rob for his immense assistance on this and making it easy to comprehend.