When you’ve got legacy code that depends on the Real-time Operating System, you have a challenge to get your code off the target for unit testing. If you want to test with the concurrency provided by the RTOS, these are not really unit tests, and you won’t be able to write thorough tests, and you need unit tests to be through.
You’re going to need to make a test-double so you can get your code off the RTOS and into your development system for unit testing. In this article we’ll go through the steps to get started.
Some silicon vendors extend the C language so the programmers can easily interact with the silicon. Using these extensions tie production code to the silicon vendors compiler and consequently the code can only run on the target system. This is not a problem during production, but is a problem for off-target unit testing.
The good news is that we may be able to get around this problem without having to change production code, one of our goals when adding tests to legacy code.
It’s day one of adding tests to your legacy C code. You get stopped dead when the compiler announces that the code you are coaxing into the test harness can’t be compiled on this machine. You are stuck on the Make it compile step of Crash to Pass.
Moving your embedded legacy C code (embedded C code without tests) into a test harness can be a challenge. The legacy C code is likely to be tightly bound to the target processor. This might not be a problem for production, but for off-target unit testing, it is a big problem.
For C we have a limited mechanisms for breaking dependencies. In my book, I describe at length link-time and function pointer substitutions, but only touch on preprocessor stubbing.
In this article we’ll look at
#include Test-Double as a way to break dependencies on a problem
My last article featured a hand crafted a spy to monitor
asm directives. Now let’s use CppUMock (the mock support companion CppUTest) to create a mock version of
Sometimes embedded developers have to use inline assembler instructions to get better control of the processor, or to improve performance. How should we deal with those when we’re doing TDD and testing off the target?
What’s the problem? The embedded
asm statements cause compilation errors if the assembler instructions are not part of the off-target test platform instruction set. Also some of the instructions might not be legal in the test environment. This article shows how to insert a test double for the
asm directives with gcc and CppUTest.
Here is a legacy code change policy for a team adopting TDD that has a legacy code base:
- Test-drive new code
- Add tests to legacy code before modification
- Test-drive changes to legacy code
Refactoring without tests is dangerous; with all the details we must keep straight, a mistake is easy to make. How many code reviews have you been in where the recommended design changes are not made because “we already tested it”? You avoid the change because it’s dangerous to change code without tests. So, the Boy Scout adds tests too. For more on Boy Scouts, see previous post.
Adding tests to legacy C or C++ code can be a challenge. Code not designed to be tested won’t naturally be testable. Dependencies will be unmanaged and invisible. Getting that first test written will hurt, a lot. Don’t despair! The first test is the hardest, but subsequent tests are much easier.
Knowing what to do and what to expect, when you start adding tests to your legacy code, can ease the journey. This article will give you an idea of what to expect when getting that first bit of C or C++ into the test harness.