Units of Behavior
The following specification (in Ruby’s rSpec) describes software that emulates a simple box. We can add items to the box, remove them, and verify whether the box contains a specific item. In each scenario, the tested behavior is emphasized.
it 'knows what has been added' do
@box = Box.new
@box.add("red pen")
expect(@box.has("red pen")).to be true
end
it 'knows what it does not have' do
@box = Box.new
@box.add("red pen")
expect(@box.has("purple smartphone")).to be false
end
it 'can remove something it has' do
@box = Box.new
@box.add("red pen")
@box.remove("red pen")
expect(@box.has("red pen")).to be false
end
Notice that add() cannot be tested without has(), and vice versa. That is, the behavior is spread out over two or three method calls on the same object. The implementations of these behaviors are encapsulated by the Box object, and the specification of each unit of behavior is clearly described by a single scenario. In other words, behavior is most clearly described in the tests.
The next example demonstrates how a single line of code can be part of multiple distinct behaviors. Imagine we are designing a retro-style volume control dial for a smartphone (Figure 1-1). The control can be set to only integer values, and our user interface (UI) developer tells us the dial will graphically “snap” to the next value as the user rotates the dial, perhaps with haptic feedback to emulate that memorable “click.”
Figure 1-1 A stereo system’s volume dial circa 1980. (Photo: atm2003/123rf).
In a brief whiteboard discussion, we decide we need a volume-control application programming interface (API) that accepts an integer from 0 to 10. Zero is effectively “off” and 10 is the maximum volume (not 11!2).
The UI developer assures us that we will not receive an out-of-range integer. However, we agree that if this somehow happens, our code will throw an IllegalArgumentException. In Java, this could look like the following:
if (volumeZeroToTen > 10 || volumeZeroToTen < 0)
throw new IllegalArgumentException("Volume cannot be " +
valueZeroToTen);
audioManager.setStreamVolume(volumeZeroToTen);
There are three scenarios:
When volumeZeroToTen is greater than 10
When volumeZeroToTen is less than 0
When volumeZeroToTen is within the acceptable range
Each of those three scenarios is different because of the first if conditional, and each deserves its own distinct unit test. The behavior is more clearly described by expressly writing tests for those three examples.
