ibooksonline

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:

src/LedDriver/LedDriver.c
 void LedDriver_TurnOn(int ledNumber)
 {
  *ledsAddress |= (1 << ledNumber);
 }

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:

src/LedDriver/LedDriver.c
 void LedDriver_TurnOn(int ledNumber)
 {
  *ledsAddress |= 1 << (ledNumber - 1);
 }
<= 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:

unity/LedDriver/LedDriverTest.c
 TEST(LedDriver, AllOn)
 {
  LedDriver_TurnAllOn();
  TEST_ASSERT_EQUAL_HEX16(0xffff, virtualLeds);
 }

And its implementation:

src/LedDriver/LedDriver.c
 void LedDriver_TurnAllOn(void)
 {
  *ledsAddress = 0xffff;
 }

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?

src/LedDriver/LedDriver.c
 void LedDriver_TurnOff(int ledNumber)
 {
  *ledsAddress &= ~(convertLedNumberToBit(ledNumber));
 }

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);
 
 #define RUNTIME_ERROR(description, parameter)\
  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.

unity/LedDriver/LedDriverTest.c
 IGNORE_TEST(LedDriver, OutOfBoundsToDo)
 {
  /* TODO: what should we do during runtime? */
 }

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