Testing with Rspec: an example using people.
"There is no Testing Boilerplate." Simply, testing follows a simple model of "if I do a thing, then I should get this other thing" course of events, and with each test usecase in a project we should simply adopt this mentality. Here we'll run out a simple case using the example of people ( yeah the ones in real life ) of how a test can be run, using rspec.
So take for example the imaginary class Person, who can walk, talk, eat, & think. For the usecase of walk, let's say we expect Person walks at an average of 4.7km per hour, so after an hour we expect the distance covered to be 4.7km from the starting distance.
Let's begin by setting up a couple of spec files for testing. Note that this will not be done in rails, but vanilla ruby. We can run these commands in terminal to get started.
mkdir lib spec
mkdir lib/sims spec/sims
touch spec/sims/person_spec.rb
We can begin by opening the person_spec.rb we just created.
module Sims
describe Sims::Person do
end
end
running rspec we'll get our first error:
`<module:Sims>': undefined local variable or method `person' for Sims:Module (NameError)
# person_spec.rb
require "sims/person"
module Sims
describe Sims::Person do
end
end
running rspec again we get that there's no file.
`require': cannot load such file -- sims/person (LoadError)
So let's make one person model.
touch lib/sims/person.rb
# person.rb
module Sims
class Person
end
end
Case for Walk
Describe a Sims person, describe how they walk.
require "sims/person"
module Sims
describe Sims::Person do
describe ".walk" do
end
end
end
Start with the simple case,
# person_spec.rb
require "sims/person"
module Sims
describe Sims::Person do
describe ".walk" do
it "changes location" do
end
end
end
end
rspec
Finished in 0.00049 seconds (files took 0.146 seconds to load) 1 example, 0 failures
Let's say you want to make better rspec info. Create a .rspec file in the top level directory of the project. Add these lines for color, and description of what we're working with:
//.rspec -c --format=doc
# person_spec.rb
require "sims/person"
module Sims
describe Sims::Person do
describe ".walk" do
it "changes location" do
expect(1).to eq(2)
end
end
end
end
rspec
Failures:
1) Sims::Person.walk changes location
Failure/Error: expect(1).to eq(2)
expected: 2
got: 1
You never want to fix multiple tests at once. Rspec gives you a specific failure on a specific line, in a nifty command:
rspec ./spec/sims/person_spec.rb:6
Usually tests using BDD and TDD follow a "Given, When, Then" flow. Given a thing When the thing does this Then the thing should have this result
# person_spec.rb
require "sims/person"
module Sims
describe Sims::Person do
describe ".walk" do
it "changes location" do
# GIVEN
person = Sims::Person.new
# WHEN
person.walk(4.5)
# THEN
expect(person.location).to eq(4.5)
end
end
end
end
rspec
1) Sims::Person.walk changes location
Failure/Error: person.walk(4.5)
NoMethodError:
undefined method `walk'
# person.rb
module Sims
class Person
def walk(distance)
end
end
end
rspec We now get our first logic error:
1) Sims::Person.walk changes location
Failure/Error: expect(person.location).to eq(4.5)
expected: 4.5
got: nil
# person.rb
module Sims
class Person
attr_reader :location
def walk(distance)
@location = 4.5
end
end
end
rspec
Sims::Person
.walk
changes location
1 example, 0 failures
General steps we take for TDD are: st=>Red op=>Green e=>Refactor You are not allowed to let your test go red while refactoring, undo.
Just because it was green, doesn't mean it's always correct. In this case, it should increase the distance, not set it to distance, as such: ``` module Sims class Person
attr_reader :location
def walk(distance)
@location += distance
end
end
end ``` rspec
1) Sims::Person.walk changes location
Failure/Error: person.walk(4.5)
NoMethodError:
undefined method `+' for nil:NilClass
We rectify this simply by initializing the distance to 0
# person_spec.rb
require "sims/person"
module Sims
describe Sims::Person do
describe ".walk" do
it "changes location" do
# GIVEN
person = Sims::Person.new
# WHEN
person.walk(4.5)
person.walk(10)
# THEN
expect(person.location).to eq(14.5)
end
end
end
end
Initialize it ```
person.rb
module Sims class Person
attr_reader :location
def initialize
@location = 0
end
def walk(distance)
@location += distance
end
end
end ```
Case for Eat
describe ".eat" do
it "changes hunger" do
person = Sims::Person.new
food = Food::Food.new
person.hunger = 10
food.weight = 2
person.eat(food)
expect(person.hunger).to eq(8)
end
end
# food.rb
module Food
class Food
attr_accessor :weight
end
end
# person.rb
def eat(food)
@hunger -= food.weight
end