Grow the Solution from Simple Beginnings
The simple-minded implementation from the previous chapter
will get more robust incrementally. Each test-drives
the implementation to a more complete state.
The next test turns on a couple of LEDs. That is enough
to force a more general and complete implementation.
Notice which LEDs are turned on in the test that follows, and see
if you can easily determine whether the expected value in
virtualLeds is correct.
| unity/LedDriver/LedDriverTest.c |
| | TEST(LedDriver, TurnOnMultipleLeds) |
| | { |
| | LedDriver_TurnOn(9); |
| | LedDriver_TurnOn(8); |
| | TEST_ASSERT_EQUAL_HEX16(0x180, virtualLeds); |
| | } |
Tests are documentation, and a successful document
should be easy to understand. If you want to have
well-written tests, you must carefully select your test
data.
Someone who works with binary and hexadecimal should
find it pretty easy to confirm that test data. I chose
LEDs 8 and 9 because the arithmetic is fairly simple
and the two bits are in separate nibbles, making their
combination easy to recognize in hex.
I expect this test to compile; there were no interface
changes. If you build and run right now, there is no
failing test, as expected. Add the test into the
TEST_GROUP_RUNNER. It fails
because we did the simplest thing that could
possibly work at the end of the previous chapter,
and it doesn’t work for this test.
| <= | make |
| | compiling LedDriverTest.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | .... |
| | TEST(LedDriver, TurnOnMultipleLeds) |
| | LedDriver/LedDriverTest.c:60: FAIL |
| | Expected 0x0180 Was 0x0001 |
| | ----------------------- |
| | 4 Tests 1 Failures 0 Ignored |
| | FAIL |
No surprise here. The test fails as expected, proving
the implementation inadequate by this test. What was
the DTSTTCPW before this test is no longer DTSTTCPW; it
doesn’t work.
Instead of just assigning *ledAddress =
1, we do some bit fiddling like this:
Will the test pass? That’s the goal. I’m bullish as I
save and build.
| <= | make |
| | compiling LedDriver.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | ... |
| | TEST(LedDriver, TurnOnLedOne) |
| | LedDriver/LedDriverTest.c:43: FAIL |
| | Expected 0x0001 Was 0x0002 |
| | . |
| | TEST(LedDriver, TurnOnMultipleLeds) |
| | LedDriver/LedDriverTest.c:60: FAIL |
| | Expected 0x0180 Was 0x0300 |
| | ----------------------- |
| | 4 Tests 2 Failures 0 Ignored |
| | FAIL |
What’s with those two failures? This was
supposed to pass! The previously passing
TEST(LedDriver,
TurnOnLedOne) and the new
TEST(LedDriver,
TurnOnMultipleLeds) are both failing.
There must be some small mistake. It can’t be big;
only one line was changed. Small logic mistakes happen
all the time. One of the big benefits of TDD is
catching small problems while they are easy to find
and fix. A key to this is detecting them right away.
If this mistake had been discovered in the fully
integrated system, finding it would take much
longer. There would have been multiple changes bundled
with it. The fault could have been with the caller
asking for the wrong LED rather than the driver
controlling the wrong LED. It’s this defect prevention
aspect of TDD that helps developers keep a rapid
steady pace.
Let’s find the problem. Look carefully at the
TEST(LedDriver, TurnOnLedOne)
output, and you can see that 0x0001 was expected but the
result was a 0x0002. The bit shifter between your ears
might tell you that the bit was shifted one position
too many to the left. No print statements or debuggers
needed, and we’ve isolated the problem:
ledNumber needs to be
converted to a bit offset by subtracting one from it
prior to using it as a shift count.
Off-by-one errors and other logic errors
are very easy to make, but their consequence can be
catastrophic as a hidden bug. In Debug-Later
Programming, mature bugs are hunted down. In
TDD, we see them in the larvae stage and squash them
before they infest the code.
One of the goals of running tests on the development
system is to find and fix most errors before running
on the hardware, avoiding extra target uploads and lengthy
Debug on
Hardware (DOH!) sessions. Of
course, there will still be problems that can be
found only on the hardware, but we can avoid DOH! for many
of the problems along the way.
The off-by-one error is fixed like this:
| <= | make |
| | compiling LedDriverTest.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | .... |
| | ----------------------- |
| | 4 Tests 0 Failures 0 Ignored |
| | OK |
Ahhh, having a passing test feels much better. It is
fixed now, never to be broken again, at least not
without us knowing right away.
Now that all tests pass, it’s a good time to look for
ways to improve the code. That bit manipulation is a
little tough on the eyes. Let’s refactor the bit
manipulation into its own helper function.
| src/LedDriver/LedDriver.c |
| | static uint16_t convertLedNumberToBit(int ledNumber) |
| | { |
| | return 1 << (ledNumber - 1); |
| | } |
| | |
| | void LedDriver_TurnOn(int ledNumber) |
| | { |
| | *ledsAddress |= convertLedNumberToBit(ledNumber); |
| | } |
It’s easier to see the ideas in the code when you
extract them and wrap them in an intention-revealing
name. You might be concerned with the added overhead
for extracting the bit manipulation. You can see I
chose a static function to
avoid adding to the global namespace. If we are really
concerned about the memory footprint, we could also
make convertLedNumberToBit an
inline[10] function or a preprocessor macro. That may be
unnecessary with today’s optimizing compilers.
Steady Progress
Each test moves the implementation closer to done.
Often, there is a natural sequence to the tests,
like the stones in a mountain stream. Sometimes there
is more than one path. Let’s hop to the next stone,
testing that we’re on the right path.
| unity/LedDriver/LedDriverTest.c |
| | TEST(LedDriver, TurnOffAnyLed) |
| | { |
| | LedDriver_TurnOn(9); |
| | LedDriver_TurnOn(8); |
| | LedDriver_TurnOff(8); |
| | TEST_ASSERT_EQUAL_HEX16(0x100, virtualLeds); |
| | } |
The test fails (assuming you installed it
in the TEST_GROUP_RUNNER).
| <= | make |
| | compiling LedDriverTest.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | ..... |
| | TEST(LedDriver, TurnOffAnyLed) |
| | LedDriver/LedDriverTest.c:68: FAIL |
| | Expected 0x0100 Was 0x0000 |
| | ----------------------- |
| | 5 Tests 1 Failures 0 Ignored |
| | FAIL |
Notice that the test turns on LED 9 before turning
on and off LED 8. Without turning on some other LED,
the current brute-force implementation would have
passed. The existing simple implementation used to
turn off LED 1 (*ledsAddress =
0;) turns off all LEDs.
LedDriver_TurnOff does not
mask the bits that should not be affected. To force
the writing of the masking code, we have to make sure
some LED will be left in the on state.
This test would be better if we had
LedDriver_TurnAllOn to work
with. Let’s comment out this test, implement the
turn all on function, and then come back to this
test. We just stepped back one stone and are taking
a different path. Here’s the test for turning all
LEDs on:
And its implementation:
Before we move on, let’s refactor out the magic
numbers for all LEDs on and off here and in
LedDriver_Create.
| src/LedDriver/LedDriver.c |
| | enum {ALL_LEDS_ON = ~0, ALL_LEDS_OFF = ~ALL_LEDS_ON}; |
| | |
| | void LedDriver_TurnAllOn(void) |
| | { |
| | *ledsAddress = ALL_LEDS_ON; |
| | } |
With the ability to turn on all LEDs, here is the
revised test for turning off any LED:
| unity/LedDriver/LedDriverTest.c |
| | TEST(LedDriver, TurnOffAnyLed) |
| | { |
| | LedDriver_TurnAllOn(); |
| | LedDriver_TurnOff(8); |
| | TEST_ASSERT_EQUAL_HEX16(0xff7f, virtualLeds); |
| | } |
After installing TEST(LedDriver,
TurnOffAnyLed) and then watching it
fail, we add the code to
LedDriver_TurnOff. I
predict it will work right the first time. What do
you think?
Tests are passing once again. Notice that
LedDriver_TurnOff took
advantage of the recently extracted
convertLedNumberToBit.
| <= | make |
| | compiling LedDriver.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | ...... |
| | ----------------------- |
| | 6 Tests 0 Failures 0 Ignored |
| | OK |
I wonder, can the software read the state of the
LEDs? The LEDs I/O-mapped address is probably write-only. It’s best not to guess. I holler to our
hardware engineer to get a quick answer:
- James:
-
Hey Gary, can we read the state of the LEDs with
software?
- Gary:
-
That’s a stupid question; of course not. Read the
schematic!
- James, a little peeved:
-
Thanks, Gary, love you like a brother.
I think Gary had a bad weekend, but I’m glad I
asked. The driver, as currently implemented, won’t
work in the real hardware because the production
code uses the LED memory location for reading and
writing. How do we prove the hardware is not read?
It’s easier than it sounds. Add a test that shows
that the driver is not getting the current LED state
from the hardware by first setting
virtualLeds to
0xFFFF. Remember that
during initialization
LedDriver_Create turns off
all the LEDs.
| unity/LedDriver/LedDriverTest.c |
| | TEST(LedDriver, LedMemoryIsNotReadable) |
| | { |
| | virtualLeds = 0xffff; |
| | LedDriver_TurnOn(8); |
| | TEST_ASSERT_EQUAL_HEX16(0x80, virtualLeds); |
| | } |
In the current implementation, the driver reads
from virtualLeds,
causing this test to fail and giving this message:
| <= | make |
| | compiling LedDriverTest.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | ....... |
| | TEST(LedDriver, TurnOnMultipleLeds) |
| | LedDriver/LedDriverTest.c:80: FAIL |
| | Expected 0x0080 Was 0xFFFF |
| | ----------------------- |
| | 7 Tests 1 Failures 0 Ignored |
| | FAIL |
After initially forgetting to install the test
case...the newly installed test fails. (I’m
going to stop mentioning that the test must be
installed into the
TEST_GROUP_RUNNER. If you
are following along writing this code, you probably
forget a couple times, and the unexpected passing
test will warn you.) To make the test pass, record
the LED’s state in a private file-scope
variable ledsImage. Initialize it in
LedDriver_Create.
| src/LedDriver/LedDriver.c |
| | enum {ALL_LEDS_ON = ~0, ALL_LEDS_OFF = ~ALL_LEDS_ON}; |
| | |
| | static uint16_t * ledsAddress; |
| | static uint16_t ledsImage; |
| | |
| | void LedDriver_Create(uint16_t * address) |
| | { |
| | ledsAddress = address; |
| | ledsImage = ALL_LEDS_OFF; |
| | *ledsAddress = ledsImage; |
| | } |
Use ledsImage in the functions
LedDriver_TurnOn,
LedDriver_TurnOff, and
LedDriver_TurnAllOn as a
record of the current LED states.
| src/LedDriver/LedDriver.c |
| | void LedDriver_TurnOn(int ledNumber) |
| | { |
| | ledsImage |= convertLedNumberToBit(ledNumber); |
| | *ledsAddress = ledsImage; |
| | } |
| | void LedDriver_TurnOff(int ledNumber) |
| | { |
| | ledsImage &= ~(convertLedNumberToBit(ledNumber)); |
| | *ledsAddress = ledsImage; |
| | } |
| | void LedDriver_TurnAllOn(void) |
| | { |
| | ledsImage = ALL_LEDS_ON; |
| | *ledsAddress = ledsImage; |
| | } |
The tests are running green again.
| <= | make |
| | compiling LedDriver.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | ....... |
| | ----------------------- |
| | 7 Tests 0 Failures 0 Ignored |
| | OK |
Let’s remove a bit more duplication and make
this code easier for future readers by extracting
*ledsAddress = ledsImage;
into a helper function.
| src/LedDriver/LedDriver.c |
| | static void updateHardware(void) |
| | { |
| | *ledsAddress = ledsImage; |
| | } |
| | |
| | void LedDriver_TurnAllOn(void) |
| | { |
| | ledsImage = ALL_LEDS_ON; |
| | updateHardware(); |
| | } |
The implementation for
LedDriver_TurnOn and
LedDriver_TurnOff looks
pretty complete, with one exception: there are no
boundary checks.
Let’s decide whether bounds checks are
needed.
Test Boundary Conditions
In
some designs, the responsibility for assuring that no
out-of-bounds LEDs are controlled may be somewhere
else. In that case, the design would not call for
boundary checking. But in this case, the
LedDriver will be used by the
application-level code, so boundary
checks are the driver’s responsibility.
This test checks the upper and lower bounds of the
legal LED values. The tests act as a detailed
requirement. The really cool part about this
document is that it executes, making sure the
requirement is met.
| unity/LedDriver/LedDriverTest.c |
| | TEST(LedDriver, UpperAndLowerBounds) |
| | { |
| | LedDriver_TurnOn(1); |
| | LedDriver_TurnOn(16); |
| | TEST_ASSERT_EQUAL_HEX16(0x8001, virtualLeds); |
| | } |
Unsurprisingly, this test compiles and runs the first time.
| <= | make |
| | compiling LedDriverTest.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | ........ |
| | ----------------------- |
| | 8 Tests 0 Failures 0 Ignored |
| | OK |
What should happen when you manipulate an
out-of-bounds LED? Should the driver write over
adjacent memory, silently ignore
the bad parameters, corrupt the stack, or produce a runtime error? Of
all those, the last one sounds best. If the code
detects the wrong LED number, there is certainly a
programming error.
Before we get to how to handle the runtime error,
let’s make sure that the out-of-bounds values do no
harm—part of a Hippocratic Oath for LED drivers.
This test exercises the driver with some fence-post
values and a way out-of-bounds value.
| unity/LedDriver/LedDriverTest.c |
| | TEST(LedDriver, OutOfBoundsChangesNothing) |
| | { |
| | LedDriver_TurnOn(-1); |
| | LedDriver_TurnOn(0); |
| | LedDriver_TurnOn(17); |
| | LedDriver_TurnOn(3141); |
| | |
| | TEST_ASSERT_EQUAL_HEX16(0, virtualLeds); |
| | } |
Running the tests...
| <= | make |
| | compiling LedDriverTest.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | ....... |
| | TEST(LedDriver, OutOfBoundsChangesNothing) |
| | LedDriver/LedDriverTest.c:100: FAIL |
| | Expected 0x0000 Was 0x0010 |
| | ----------------------- |
| | 9 Tests 1 Failures 0 Ignored |
| | FAIL |
I thought this would pass, just by accident, but it
didn’t. When you shift a bit totally out of the
variable, shouldn’t the variable be zero? If
inquiring minds really need to know, insert
TEST_ASSERT_EQUAL_HEX16(0,
virtualLeds) after each
LedDriver_TurnOn call, and
read the sidebar
Inquiring Minds and LedDriver_TurnOn(3141)
to understand the
test output.
Sometimes testing unhandled out-of-bounds conditions
will crash the test run. For instance, an
out-of-bounds for an array on the stack can corrupt
the stack. Out-of-bounds in this case won’t do any
damage; it just gives an odd result.
To make the test pass, add guard clauses to
LedDriver_TurnOn and
LedDriver_TurnOff like
this:
| src/LedDriver/LedDriver.c |
| | void LedDriver_TurnOn(int ledNumber) |
| | { |
| | if (ledNumber <= 0 || ledNumber > 16) |
| | return; |
| | |
| | ledsImage |= convertLedNumberToBit(ledNumber); |
| | updateHardware(); |
| | } |
| | |
| | void LedDriver_TurnOff(int ledNumber) |
| | { |
| | if (ledNumber <= 0 || ledNumber > 16) |
| | return; |
| | |
| | ledsImage &= ~(convertLedNumberToBit(ledNumber)); |
| | updateHardware(); |
| | } |
Run the makefile, and see the tests pass:
| <= | make |
| | compiling LedDriverTest.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | ......... |
| | ----------------------- |
| | 9 Tests 0 Failures 0 Ignored |
| | OK |
Wait a second. The code just got ahead of the tests.[11] The guard clause in
LedDriver_TurnOff is not
tested. The copied code from
LedDriver_TurnOn was tested
in TEST(LedDriver,
OutOfBoundsChangesNothing), but it is
not tested in its new home.
Sure, you feel perfectly safe copying tested code,
but beware—the two copies are likely to diverge in
the future. Tests are not just for getting the code
right in the first place but keeping it right over
the long run. So, delete or comment out the pasted
code and add an out-of-bounds test for
LedDriver_TurnOff. You want
to see it fail first.
Let’s rename TEST(LedDriver,
OutOfBoundsChangesNothing) to
TEST(LedDriver,
OutOfBoundsTurnOnDoesNoHarm)—the name now
focusses on the out-of-bounds behavior of
LedDriver_TurnOn. A quick
copy/paste/edit gives the
LedDriver_TurnOff variant
of the test.
| unity/LedDriver/LedDriverTest.c |
| | TEST(LedDriver, OutOfBoundsTurnOffDoesNoHarm) |
| | { |
| | LedDriver_TurnOff(-1); |
| | LedDriver_TurnOff(0); |
| | LedDriver_TurnOff(17); |
| | LedDriver_TurnOff(3141); |
| | |
| | TEST_ASSERT_EQUAL_HEX16(0, virtualLeds); |
| | } |
The test should have failed, but it passed!
| <= | make |
| | compiling LedDriverTest.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | .......... |
| | ----------------------- |
| | 10 Tests 0 Failures 0 Ignored |
| | OK |
Copy/paste almost got us in trouble...again;
the LEDs are all off to start with! The LEDs must be
on to be able to tell whether
LedDriver_TurnOff does its
job. This should help.
| unity/LedDriver/LedDriverTest.c |
| | TEST(LedDriver, OutOfBoundsTurnOffDoesNoHarm) |
| | { |
| | LedDriver_TurnAllOn(); |
| | |
| | LedDriver_TurnOff(-1); |
| | LedDriver_TurnOff(0); |
| | LedDriver_TurnOff(17); |
| | LedDriver_TurnOff(3141); |
| | |
| | TEST_ASSERT_EQUAL_HEX16(0xffff, virtualLeds); |
| | } |
Now we get the failure and can copy and paste in the
LED guard clause.
Now back to the runtime error. Rather than silently
having a runtime error, the driver should let
someone know about the error. Let’s invoke the
RUNTIME_ERROR macro.
RUNTIME_ERROR logs an error
recording the message provided as well as the file
and line number. Here is its declaration:
| include/util/RuntimeError.h |
| | void RuntimeError(const char * message, int parameter, |
| | const char * file, int line); |
| | |
| | |
| | RuntimeError(description, parameter, __FILE__, __LINE__) |
In production, RuntimeError
puts an entry into an event log. During test, stub
out RuntimeError so the
last error can be captured and checked. The stub
header looks like the following:
| mocks/RuntimeErrorStub.h |
| | void RuntimeErrorStub_Reset(void); |
| | const char * RuntimeErrorStub_GetLastError(void); |
| | int RuntimeErrorStub_GetLastParameter(void); |
Here’s the stub implementation:
| mocks/RuntimeErrorStub.c |
| | #include "RuntimeErrorStub.h" |
| | |
| | static const char * message = "No Error"; |
| | static int parameter = -1; |
| | static const char * file = 0; |
| | static int line = -1; |
| | |
| | void RuntimeErrorStub_Reset(void) |
| | { |
| | message = "No Error"; |
| | parameter = -1; |
| | } |
| | |
| | const char * RuntimeErrorStub_GetLastError(void) |
| | { |
| | return message; |
| | } |
| | |
| | void RuntimeError(const char * m, int p, const char * f, int l) |
| | { |
| | message = m; |
| | parameter = p; |
| | file = f; |
| | line = l; |
| | } |
| | |
| | int RuntimeErrorStub_GetLastParameter(void) |
| | { |
| | return parameter; |
| | } |
As you can see, the stub version of
RuntimeError just captures
the error description. The other two functions give
a way to reset the stub and access to the captured
message.
The stub version of
RuntimeError is linked in
during test. It lets the test check that
out-of-bounds produces a
RuntimeError. This is shown in the
following code.
| unity/LedDriver/LedDriverTest.c |
| | TEST(LedDriver, OutOfBoundsProducesRuntimeError) |
| | { |
| | LedDriver_TurnOn(-1); |
| | TEST_ASSERT_EQUAL_STRING("LED Driver: out-of-bounds LED", |
| | RuntimeErrorStub_GetLastError()); |
| | TEST_ASSERT_EQUAL(-1, RuntimeErrorStub_GetLastParameter()); |
| | } |
Watch the test fail.
| <= | make |
| | compiling LedDriverTest.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | ........... |
| | TEST(LedDriver, OutOfBoundsProducesRuntimeError) |
| | unity/LedDriver/LedDriverTest.c:138: FAIL |
| | Expected 'LED Driver: out-of-bounds LED' Was 'No Error' |
| | ----------------------- |
| | 11 Tests 1 Failures 0 Ignored |
| | FAIL |
Now add the call to
RUNTIME_ERROR; the test
passes.
| <= | make |
| | compiling LedDriver.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | ........... |
| | ----------------------- |
| | 11 Tests 0 Failures 0 Ignored |
| | OK |
Executable Reminders
We decided that the out-of-bounds LED number should
issue a runtime error. What if we could not decide?
We could add an item to the test list and come back
to it later. We could also add an
executable reminder.
Most unit tests harnesses have some provision for
ignoring a test. In Unity and CppUTest, change
TEST to
IGNORE_TEST. The ignored
test must still compile, but it does not run. You
can use ignored tests as an executable reminder. A
good thing about an executable reminder is that it
will be hard to lose. You see evidence of it with
every test run.
Notice the ! in the sequence of dots. Also the
ignored count is now 1. The reminder is subtle, but it is there.
| <= | make |
| | compiling LedDriverTest.c |
| | Linking BookCode_Unity_tests |
| | Running BookCode_Unity_tests |
| | Unity test run 1 of 1 |
| | ..........! |
| | ----------------------- |
| | 12 Tests 0 Failures 1 Ignored |
| | OK |
To see what test is ignored, run the test with the
verbose flag set. When verbose is switched on, all
tests are announced before they are run, including
any ignored tests.
| <= | $ ./BookCode_Unity_tests -v |
| | TEST(LedDriver, LedsOffAfterCreate) PASS |
| | TEST(LedDriver, TurnOnLedOne) PASS |
| | TEST(LedDriver, TurnOffLedOne) PASS |
| | TEST(LedDriver, TurnOnMultipleLeds) PASS |
| | TEST(LedDriver, TurnOffAnyLed) PASS |
| | TEST(LedDriver, AllOn) PASS |
| | TEST(LedDriver, LedMemoryIsNotReadable) PASS |
| | TEST(LedDriver, UpperAndLowerBounds) PASS |
| | TEST(LedDriver, OutOfBoundsTurnOnDoesNoHarm) PASS |
| | TEST(LedDriver, OutOfBoundsTurnOffDoesNoHarm) PASS |
| | TEST(LedDriver, OutOfBoundsProducesRuntimeError) PASS |
| | IGNORE_TEST(LedDriver, OutOfBoundsToDo) |
| | 12 Tests 0 Failures 1 Ignored |
| | OK |