This is part 1 of a planned 3 part series.
Introduction:
The challenge was to implement the code for a checkout system that handles pricing schemes such as “apples cost 50 cents, three apples cost $1.30.”
The basic guidelines on how to implement the kata can be found here
The idea is to use the Red, Green, Refactor approach.
Write a failing test, and then write little code to get the failing test to pass and then clean up the code.
Initialising project:
- Navigate to an appropriate directory:
~ cd practice
- Create a new directory & change to it:
~ mkdir checkout && cd $_
- Create and edit Gemfile:
vim Gemfile
(vim is being used as the editor for this project)
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
gem 'rspec'
- Press
ctrl + z
to navigate back to terminal and then typebundle binstubs --all
(includingbinstubs --all
afterbundle
is a shortcut to not usebundle exec
when running therspec
command) - Type
bin/rspec --init
to initialise rspec (aspec
folder will be created)
Writing the first test:
- Create a new file under the spec folder:
touch spec/check_out_spec.rb
Write the first failing test which checks when nothing has been scanned;
RSpec.describe 'checkout' do context 'when nothing has been scanned' do it 'shows a total of zero' do expect(total).to eq 0 end end end
- As expected, this test fails when
⎵ + t
is pressed on the keyboard;
./bin/rspec spec/check_out_spec.rb
Randomized with seed 59413
F
Failures:
1) checkout when nothing has been scanned shows a total of zero
Failure/Error: expect(total).to eq 0
NameError:
undefined local variable or method `total' for #<RSpec::ExampleGroups::Checkout::WhenNothingHasBeenScanned "shows a total of zero" (./spec/check_out_spec.rb:3)>
# ./spec/check_out_spec.rb:5:in `block (3 levels) in <top (required)>'
Finished in 0.00169 seconds (files took 0.04863 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/check_out_spec.rb:3 # checkout when nothing has been scanned shows a total of zero
Randomized with seed 59413
shell returned 1
Press ENTER or type command to continue
Let us incrementally get this test to pass. The feedback loop with TDD is second to none as the error actually tells us what to do next
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 end
In the above code snippet, two lines have been added;
pricing_rules = :Rule
andcheckout = CheckOut.new(pricing_rules)
The error is now different;
~
./bin/rspec spec/check_out_spec.rb
Randomized with seed 59719
F
Failures:
1) checkout when nothing has been scanned shows a total of zero
Failure/Error: checkout = CheckOut.new(pricing_rules)
NameError:
uninitialized constant CheckOut
# ./spec/check_out_spec.rb:5:in `block (3 levels) in <top (required)>'
Finished in 0.00174 seconds (files took 0.04954 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/check_out_spec.rb:3 # checkout when nothing has been scanned shows a total of zero
Randomized with seed 59719
shell returned 1
Press ENTER or type command to continue
Now we create add a class: CheckOut
as the code below;
class CheckOut
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
end
Now, this is the error returned;
~
./bin/rspec spec/check_out_spec.rb
Randomized with seed 45459
F
Failures:
1) checkout when nothing has been scanned shows a total of zero
Failure/Error: checkout = CheckOut.new(pricing_rules)
ArgumentError:
wrong number of arguments (given 1, expected 0)
# ./spec/check_out_spec.rb:9:in `initialize'
# ./spec/check_out_spec.rb:9:in `new'
# ./spec/check_out_spec.rb:9:in `block (3 levels) in <top (required)>'
Finished in 0.00264 seconds (files took 0.05216 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/check_out_spec.rb:7 # checkout when nothing has been scanned shows a total of zero
Randomized with seed 45459
shell returned 1
Press ENTER or type command to continue
Now we can write code to solve the above error. It would look something like so;
class CheckOut
def initialize(pricing_rules)
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
end
For this, we get the undefined method total
as the error shows;
~
./bin/rspec spec/check_out_spec.rb
Randomized with seed 46712
F
Failures:
1) checkout when nothing has been scanned shows a total of zero
Failure/Error: total = checkout.total
NoMethodError:
undefined method `total' for #<CheckOut:0x000000013952bd60>
# ./spec/check_out_spec.rb:13:in `block (3 levels) in <top (required)>'
Finished in 0.00174 seconds (files took 0.05301 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/check_out_spec.rb:10 # checkout when nothing has been scanned shows a total of zero
Randomized with seed 46712
shell returned 1
Press ENTER or type command to continue
To fix the error, add a method: total
as shown below;
class CheckOut
def initialize(pricing_rules)
end
def total
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
end
We now get another error as shown;
~
./bin/rspec spec/check_out_spec.rb
Randomized with seed 15684
F
Failures:
1) checkout when nothing has been scanned shows a total of zero
Failure/Error: expect(total).to eq 0
expected: 0
got: nil
(compared using ==)
# ./spec/check_out_spec.rb:18:in `block (3 levels) in <top (required)>'
Finished in 0.00714 seconds (files took 0.05137 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/check_out_spec.rb:13 # checkout when nothing has been scanned shows a total of zero
Randomized with seed 15684
shell returned 1
Press ENTER or type command to continue
Now, to get this to pass, we need to change the total
method to return 0
;
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
end
Below is the output of the passing test;
~
./bin/rspec spec/check_out_spec.rb
Randomized with seed 9210
.
Finished in 0.00187 seconds (files took 0.04874 seconds to load)
1 example, 0 failures
Randomized with seed 9210
Press ENTER or type command to continue
This is just one of the tests though. Other tests need to be done i.e. when A has been scanned
, when B has been scanned
, when C has been scanned
, when D has been scanned
This concludes part 1.
In part 2, we will tackle how to handle tests for the scanning of A, B, C & D.
In part 3, we will do some refactoring to make the code better.
Shout out to Rob for his immense assistance on this and making it easy to comprehend.