The mock object approach to test-driven development
Why Mocks Improve Development
Mocks play a critical role in the TDD process, making it possible to develop code using a top-down approach. This means that you start at the beginning of the program, the main loop, and work your way down to the lowest-level peripheral drivers. When you get to a point where you need a function, or a module that doesn’t exist, you simply mock it out and continue with the current module. Developing this way has several distinct advantages. First, it allows each module’s API to evolve naturally. No time is wasted coding features which may go unused. Second, it saves you from having to predict the interface a given module will need ahead of time. The growth of each interface is constrained to exactly what is needed by the rest of the application.
Mocks also improve the development process by facilitating a methodology known as interaction-based testing. When using interaction-based testing the developer need not be concerned with the internal state of objects. The tests simply verify that a given module interacts with other modules in an expected way and produces an expected result based on a given set of inputs. In the example above, remember that when we tested the LED controller we did not assert that any output pins were actually turned on. We did not concern ourselves with the resulting state but rather verified the interaction of the LED module with the GPIO module.
TDD and Hardware Interaction
Many embedded developers resist using TDD because of the hardware interactions that are inherent to embedded applications. How do we effectively get around this problem? The answer is by using encapsulation (Figure 4). Let’s say, for example, your device communicates with a PC via a UART peripheral. A smart way to approach this would be to create a UART driver. The UART driver would consist of basic functions like SetBaudRate(), SendString(), and ReadLine(). This way, all of the device specific code is contained within the UART driver. If you ever have to change microcontrollers, all the code you need to change would be isolated in one module. Then, the rest of your application can be easily tested based on its interactions with the driver. Developing this way leads to a well structured, object-oriented application.

Figure 4. Hardware interactions can be tested by isolating device specific code in a driver which can be easily mocked.
What About Special Function Registers?
As you may have guessed, there comes a point when there is nothing left to mock. For example, if we were to write tests for our GPIO module above, how would we test that the correct output pin is actually being turned on? This is where other components of the testing framework come into play.
In general, hardware peripherals are controlled via Special Function Registers (SFRs). Ideally, we should have tests to verify that these registers are set correctly. The way in which we verify this will depend on how our testing framework is configured. For example, if the framework is set to run tests on a simulator, the SFR values can be checked in the same way as normal variables.
However, if a simulator is not available, a common alternative is to create an SFR header file. This header file will contain dummy variables that match the SFRs available on the device. You test against these dummy variables as if they were the real SFRs. For a more concise explanation, read James Grenning’s book, Test Driven Development for Embedded C.
Is TDD Really Worth My Time?
The demand for products with embedded electronics is exploding. This increased demand requires embedded developers to work faster and produce more without sacrificing the quality of their final product. TDD makes this possible by increasing the speed and accuracy of development. Will TDD take time to learn? Yes. Will it change the way you write code? Yes. Is it worth it? Without a doubt.
Not only do you get instant verification of your code using TDD, but you also have a well organized code base as a result. In addition, you eliminate nearly all of the time-consuming, on-target, IDE-based debugging that would otherwise be necessary. Utilizing Test Driven Development helps developers produce higher quality work and gain more confidence. Higher confidence equates to less stress, greater productivity, and a better final product.
Jordan Schaenzle is an embedded software specialist at Atomic Object in Grand Rapids, MI. He earned his BSE with a concentration in Electrical Engineering at Calvin College in 2007. Prior to working at Atomic Object, Jordan designed electronics for automated astronomy equipment.
Resources:
1 - Complete Example Source Code
2 - Throw The Switch
3 - Test Driven Development for Embedded C, by James Grenning
4 - CppUTest
5 - Google Code
6 - Google Mock


Loading comments... Write a comment