Stubbing
Stubbing is a technique used in unit testing to replace a routine in a system under test with a simpler routine whose behavior can be specified in the test. Stubbing allows you to limit the amount of code you are testing, which can make that test easier to design and faster to run. RPicSim does not have any special features to support stubbing, but it can easily be accomplished using features already present.
A basic stub
To stub a method in the most basic way, you can do something like this:
every_step do
if pc.value == label(:foo).address
sim.return
end
end
The example above just alters our simulation so that whenever the
foo
subroutine is called, instead of running as normal it will
return immediately using RPicSim::Sim#return.
The example above can be expanded in many ways: You might read and write from variables. You might record information about how the subroutine was called.
A stub that counts
If you want to know how many times the stubbed routine is called, you could do this:
@foo_count = 0
every_step do
if pc.value == label(:foo).address
@foo_count += 1
sim.return
end
end
Using a Ruby instance variable @foo_count
instead of a simple
local variable means that this code could go in a before hook and the code
that checks the count could be in the main part of the RSpec example.
A stub that records parameters
You might want to test that the right parameters are getting supplied to the stubbed routine. To capture information about the stubbed routine's parameters or anything else about the state of the simulation, you could use a Ruby array:
@foo_calls = []
every_step do
if pc.value == label(:foo).address
@foo_calls << { a: foo_param_a.value, b: foo_param_b.value }
sim.return
end
end
In your RSpec examples, you can test that the routine was called the right number of times and with the expected parameters:
expect(@foo_calls).to eq [ {a: 1, b: 25}, {a: 2, b: 24 } ]
LongDelay example
This is a more complete example showing how to make a simple stub that counts the number of times it was called.
Here is a minimal MPASM assembly program with two routines. The
bigDelay
routine delays for a long time using a 16-bit counter
and a loop. The cooldown
routine either calls
bigDelay
once or twice depending on some condition.
#include p10F322.inc
__config(0x3E06)
udata
hot res 1
counter res 2
code 0
cooldown:
btfsc hot, 0
call bigDelay
call bigDelay
return
bigDelay:
movlw 255
movwf counter
movlw 255
movwf counter + 1
delayLoop:
decfsz counter, F
goto delayLoop
decfsz counter+1, F
goto delayLoop
return
end
Suppose we want to write a unit test for the logic in the
cooldown
method. We could just run the subroutine in various
conditions and see how long it takes to finish. However, that test could be
very slow to run. Instead, we should stub the bigDelay
method
and make the stub count how many times it was called.
In spec/spec_helper.rb
, we make a simulation class that points
to the compiled COF file. There is nothing special here:
require 'rpicsim/rspec'
class LongDelay < RPicSim::Sim
use_device "PIC10F322"
use_file File.dirname(__FILE__) + "../firmware/dist/firmware.cof"
def_var :hot, :uint8
end
In spec/cooldown_spec.rb
, we stub the bigDelay
routine and test cooldown
to make sure it calls
bigDelay
the right number of times:
require 'rpicsim/rspec'
describe "cooldown" do
before do
start_sim Firmware::LongDelay
# Stub the "bigDelay" function because it takes a long time to run.
# Also, count how many times it was called.
@big_delay_count = 0
every_step do
if pc.value == label(:bigDelay).address
@big_delay_count += 1
sim.return
end
end
end
context "when the room is cool" do
before do
hot.value = 0
end
it "only does one big delay" do
run_subroutine :cooldown, cycle_limit: 100
expect(@big_delay_count).to eq 1
end
end
context "when the room is hot" do
before do
hot.value = 1
end
it "does two big delays" do
run_subroutine :cooldown, cycle_limit: 100
expect(@big_delay_count).to eq 2
end
end
end
This makes our test much faster and allows us to just test the behavior of
the cooldown
routine.