-
Notifications
You must be signed in to change notification settings - Fork 61
Robot damage example
This annotated example was taken from a programmable robot kata. This spec deals with the damage dealt to a robot when it gets hit with a bullet.
01) require 'spec_helper'
02) require 'botlab/robot_runner'
03)
04) describe RobotRunner do
05) Given(:runner) { RobotRunner.new }
06)
07) Invariant { (runner.energy < 0).should == runner.dead? }
08)
09) context "when hit" do
10) Given!(:original_energy) { runner.energy }
11) Given(:bullet) { flexmock(:on, Bullet, energy: bullet_energy) }
12)
13) When(:result) { runner.hit(bullet) }
14)
15) Invariant { result.should == bullet_energy }
16) Invariant { runner.energy.should == original_energy - bullet_energy }
17)
18) context "lightly" do
19) Given(:bullet_energy) { 5 }
20) Then { runner.should_not be_dead }
21) end
22)
23) context "pretty hard" do
24) Given(:bullet_energy) { original_energy }
25) Then { runner.should_not be_dead }
26) end
27)
28) context "really hard" do
29) Given(:bullet_energy) { original_energy + 1 }
30) Then { runner.should be_dead }
31) end
32) end
28) end
Line 5: The robot runner is responsible for interfacing the Robot AI (not shown here for brevity) and the robot simulation framework. Here we create a single runner for testing.
Line 7: The robot runner defines a boolean method named "dead?" that should be true whenever the energy level drops below zero. By specifying that the two are equivalent here in an invariant, we hold that this relationship between energy_level and dead? should hold true for all the scenarios in this specification.
Line 9: The following specification applies when a robot is hit by a bullet.
Line 10: We're going to compare the change in runner energy with the original value, so we grab the original value here with a non-lazy Given!. This captures the energy level before the When clause is run.
Line 11: Here we supply the bullet that will be used in the test. Since a real bullet is cumbersome to create and all we need is an object with an energy level, we are using a mock here. The mock is based on the Bullet class, so if the name of the method changes, the mock will fail. This helps prevent fantasy tests that mock the wrong method. We use the value bullet_energy
to set the bullet energy level. We will define bullet_energy
a bit later.
Line 13: This is the code under test. All the assertions in this context (and child contexts) should be centered on verifying this code.
Line 15: Specify that in all scenarios covered by this invariant, the result of the hit call (in the When clause) will be the amount of the bullet energy.
Line 16: Specify that in all scenarios covered by this invariant, the energy of the robot will be reduced by the amount of energy in the bullet.
Line 18: In the first nested scenario, we are going to explore what happens when the robot is hit lightly by the bullet.
Line 19: Specify that the bullet energy in this scenario is only 5 points. Notice at this point we finally define a given that is assumed in line 11.
Line 20: With that amount of energy, the robot will not be dead. At this point, all three invariants in scope (from lines 7, 15 and 16) are checked as well. This makes sure the dead method and energy methods are in sync, the robots energy level is appropriately changed, and the the result of the hit method is correct.
Line 23-26: Another nested scenario, this time exploring what happens when the robots energy level is reduced to zero. All the invariants apply here as well.
Lines 28-31: The final scenario where we explore reducing the robots energy to below zero. Again, the invariants are applied here.