Check boundary values Check out-of-bounds values
3.5 Test-Drive the Interface Before the Internals
A good interface is critical for a well-designed module. The first few tests drive the interface design. The focus on the interface means that we’re working from the outside of the code being developed to the inside. The test, as the first user of the interface, gives the callers (or client code) perspective of how to use the code being developed. Starting from the user’s perspective leads to more usable interfaces.
I also usually let the first few tests exercise some boundary condition in the code being developed. Choose a simple case but one that exercises a boundary.
Report erratum this copy is (P1.0 printing, April, 2011)
Download from Wow! eBook <www.wowebook.com>
TEST-DRIVE THEINTERFACEBEFORE THEINTERNALS 62
Bob Martin’s Three Laws of TDD∗
Bob Martin describes Test-Driven Development using these three simple rules:
• Do not write production code unless it is to make a failing unit test pass.
• Do not write more of a unit test than is sufficient to fail, and build failures are failures.
• Do not write more production code than is sufficient to pass the one failing unit test.
Even though this sounds restrictive, it is a very productive and fun way to develop software.
. Found athttp://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd
The code behind the interface starts with hard-coded return results, so it feels like nothing is being tested. The point is not testing but driving the interface design and getting the simple boundary tests in place.
The main purpose of the driver is to turn LEDs on and off. From the schematic, we found that the LEDs are numbered 01 through 16. To turn on LED 01, when no other LEDs are on, the driver writes 0x0001 to the LED’s memory-mapped address. Turning on LED 01 in the test should result in settingvirtualLedto 1, as shown in the following test:
Download unity/LedDriver/LedDriverTest.c
TEST(LedDriver, TurnOnLedOne) {
uint16_t virtualLeds;
LedDriver_Create(&virtualLeds);
LedDriver_TurnOn(1);
TEST_ASSERT_EQUAL_HEX16(1, virtualLeds);
}
Notice that in this test ledsAddress is not first initialized to0xFFFF. That was significant for the LedDriver_Create( ) function, but not here. The initial state does not really matter becauseLedDriver_Create( ) takes care of it. This test produces a compilation error as expected.
⇒ make
compiling LedDriverTest.c
LedDriver/LedDriverTest.c: In function 'TEST_LedDriver_TurnOnLedOne_':
TEST-DRIVE THEINTERFACEBEFORE THEINTERNALS 63
LedDriver/LedDriverTest.c:23: warning: implicit declaration of function 'LedDriver_TurnOn' Linking BookCode_Unity_tests
Undefined symbols:
"LedDriver_TurnOn", referenced from:
TEST_LedDriver_TurnOnLedOne_ in LedDriverTest.o ld: symbol(s) not found
To get rid of the compilation error, add the interface-function prototype to the module’s interface declaration in the header file like this:
Download include/LedDriver/LedDriver.h
void LedDriver_TurnOn(int ledNumber);
If you typed everything right, the compilation error is gone. Your reward is a link error.
⇒ make ld: symbol(s) not found
To get rid of the link error, again add a skeletal, but wrong, implemen-tation to the .cfile.
Download src/LedDriver/LedDriver.c
void LedDriver_TurnOn(int ledNumber) {
}
Building and running now should result in a failing test. Here’s what we get: ---1 Tests 0 Failures 0 Ignored OK
Tests pass, but something is wrong. Notice there is only one test in the run. We better update the TEST_GROUP_RUNNER( ) so it knows about our new test.
Report erratum this copy is (P1.0 printing, April, 2011)
Download from Wow! eBook <www.wowebook.com>
TEST-DRIVE THEINTERFACEBEFORE THEINTERNALS 64
There is no need to add anything to main( ) this time, because the TEST_GROUP_RUNNER( ) is already installed. Witness the successful link and the new test failure:
⇒ make Expected 0x0001 Was 0x0000
---2 Tests 1 Failures 0 Ignored FAIL
Now there’s a failure, right on cue. To get this test to pass, TurnOn( ) will need access to the LED’s address that was passed into the Led-Driver_Create( ). Add a private file-scope variable to the .c file, and ini-tialize it like this:
Download src/LedDriver/LedDriver.c
static uint16_t * ledsAddress;
void LedDriver_Create(uint16_t * address) {
ledsAddress = address;
*ledsAddress = 0;
}
And finally, do the simplest thing possible to get this test to pass. In this case, write a 1 to the LED’s address in memory:
Download src/LedDriver/LedDriver.c
void LedDriver_TurnOn(int ledNumber) {
*ledsAddress = 1;
}
Build the code, and watch it pass the two tests:
⇒ make
compiling LedDriver.c
Linking BookCode_Unity_tests
TEST-DRIVE THEINTERFACEBEFORE THEINTERNALS 65
Running BookCode_Unity_tests ..
---2 Tests 0 Failures 0 Ignored OK
Writing and passing this test helped accomplish a couple things: it defined the interface for one driver function and confirms our approach for intercepting writes going to the hardware. But I bet there is some-thing bothering you.
The Implementation Is Wrong!
Like most engineers, you are probably a little uncomfortable with hard-coding something that is obviously wrong. The final implementation should only set the lowest order bit. But, if you think about it, it is exactly right for the tests written so far. If we were not practicing TDD, where more tests are to follow, leaving this wrong implementation could result in a bug. But we are doing TDD and will write the tests that reveal this weakness.
I can’t imagine getting through our test list and leaving this wrong implementation in place. But if you find yourself hard-coding some-thing that is not covered in the current test list, write the test to reveal the weakness immediately or add another item to the test list.
The Tests Are Right
With the implementation being incomplete, you might think that noth-ing is benoth-ing tested. Big deal! The test makes sure that a variable is set to one!
Try to think about it a different way. The tests are right! They are a very valuable by-product of TDD. These simple implementations test our tests. Watching the test case fail shows that the test can detect a wrong result. Hard-coding the right answer shows that the test case can detect the right result. The test is right and valuable, even though the production code is incomplete. Later, as the implementation evolves, these seemingly trivial tests will test important behavior and boundary conditions. In essence, we’re closing a vice around the code under test, holding the behavior steady (see the sidebar on the following page).
Don’t worry, the production code won’t be hard-coded and incomplete for long. As soon as you need to turn on a different LED, the hard-coded
Report erratum this copy is (P1.0 printing, April, 2011)
Download from Wow! eBook <www.wowebook.com>
TEST-DRIVE THEINTERFACEBEFORE THEINTERNALS 66
Software Vice
“When we have tests that detect change, it is like having a vice around our code. The behavior of the code is fixed in place. When we make changes, we can know that we are only changing one piece of behavior at a time. In short, we’re in control of our work.” —Michael Feathers, Working Effectively with Legacy Code [Fea04]
value will have to go. The real implementation is not much more diffi-cult, but I ask you to resist the temptation to put in more code than is needed by the current test. We’re evolving the design. The problem with adding more code than the current tests require is that you probably won’t write all the tests you need to keep future, and present, bugs out of the code.
Adding code before it is needed by the tests adds complexity. Sometimes you will be wrong about the need, resulting in carrying the complexity unnecessarily. Also, there is no end to the thinking “I will need it.”
Where should you stop? In practicing TDD, we stop when the code is not needed by the current tests. Loose ends are cataloged in the test list.
TDD is structured procrastination. Put off writing the right production code until the tests force us to. Implementation completeness, the ulti-mate objective, is reached only after all the correct tests are in place.
Choose the Next Test
What test is next? We could write a new test that would force us to eliminate the simple-minded implementation. But I’d rather evolve the interface to get a better picture of the module being built. Let’s turn off the LED just turned on. Turn on and turn off complement each other and will come in handy in the coming tests that verify that LED manipulations do not interfere with each other. One other point, we could actually deploy this code to the target if all we needed to do was turn on LED 1. It’s settled—let’s write a test to turn off LED 1:
TEST-DRIVE THEINTERFACEBEFORE THEINTERNALS 67
I’m not going to show all the steps this time. Go ahead and add the LedDriver_TurnOff( ) prototype to the .hfile, add an empty implementation of the function to the .c files, and install the test case into the test group runner just like before. Incrementally we’re getting rid of compiler errors and then linker errors. The new test builds but fails because LedDriver_TurnOff( ) doesn’t actually turn anything off.
⇒ make Expected 0x0000 Was 0x0001
---3 Tests 1 Failures 0 Ignored FAIL
You are probably getting uncomfortable again, because you know I’m going to make you hard-code the LED value again to get this test to pass. Right you are. Make the code pass like this:
Download src/LedDriver/LedDriver.c
void LedDriver_TurnOff(int ledNumber) {
*ledsAddress = 0;
}
All the tests pass again.
⇒ make ---3 Tests 0 Failures 0 Ignored OK
Report erratum this copy is (P1.0 printing, April, 2011)
Download from Wow! eBook <www.wowebook.com>
INCREMENTALPROGRESS 68
At this point, the interface of the LED driver is taking shape. We have three tests and a skeletal implementation of the driver. We’ll return to the code in the next chapter, but first let’s discuss these small steps we’re taking.