Conference Video - Deep Stack – Tracer Bullets from ADC to Browser
A blank page can be very intimidating, even for a Test-driven developer. Where do we start? Write a test, right? Not always.
more...You are getting started with TDD, but have existing code? You want to get some of your challenging C/C++ code under test? You have run into some apparent show stoppers? Don't give up! This article contains a step by step recipe to help get your code into a test harness. It also contains a series of C/C++ code problems that get in the way of unit testing. Each problem named comes with one or more suggested solutions. Also, many of the solutions provide links to articles with more detail.
Many of the problems described arise from trying to get embedded systems code that has only been compiled with the target hardware cross-compiler, to compile off-target. These problems are not unheard of for non-embedded C/C++, so any C/C++ programmer can get some insight into getting your legacy code under test using this approach. Test problems come from dependencies, so If you can relate the C/C++ specific advice to your language, there is something here for non-C programmers as well.
To help you get started, I've created a CppUTest Starter Kit on github that you can use to help get started. The started project describes how to setup your environment and has example tests, code, and mocks.
The approach the solving these problems follows the Crash to Pass algorithm, described in my book and in the linked article. To summarize, Crash to Pass describes a step by step approach to getting problem code under test.
#include
s and adjusting the include path in the unit test build. This can be frustrating.
int my_problem_depedndency(int p) { return -1; }
. Along the way, you may run into one of more of these specific road-blocks to getting your code under test.
The legacy code depends on a target specific header file that won’t compile off target.
Introduce a #include Test Double See #include Test Double. By the way, always prefer to use the real header. Only do this after discovering that using the real header would require changing it. If that file is from a third party, changing it is not really sustainable.
The legacy code depends on a header file with many outgoing problem dependencies that the code under test needs very little from.
Introduce a #include Test Double Again, prefer the real header, fake it if you must. See #include Test Double
The legacy code uses non-standard keywords that won’t compile off-target
Make the non-standard keywords go away with a forced include. See Hiding Non-standard C Keywords for Off-Target Testing.
The legacy code has asm
instructions that won’t compile off-target
Make asm
go away using forced include
Introduce an AsmSpy
to capture the instruction stream and check it in a test case See Spying on Embedded ‘asm’ directives
The legacy code interacts with OS concurrency functions.
Create stub implementation for the OS calls. Keep the stubs very simple at first. Then evolve them to meet the needs of the test case. See three part article series: Unit testing RTOS dependent code – RTOS Test-Double
#pragma
The legacy code has #pragma
instructions that won’t compile off-target
Adjust the compiler settings to ignore unknown #pragmas. For gcc use: CFLAGS += -Wno-unknown-pragmas
restrict
keyword won't compile with gnu g++The legacy code uses restrict
and won’t compile in a C++ test case
Adjust the gnu g++ compiler settings to : CXXFLAGS += -Drestrict=__restrict__
You have static functions and data, that your tests can't see. If you need to directly access hidden functions and data, your code is telling you it is not modular. But you first have to add tests before you change it.
Use preprocessor to make static go away. Now there are in the global namespace, but are not advertised in a header file.
Create a test case and #include the c file in the test, giving full access. See Accessing static Data and Functions in Legacy C — Part 1.
Create a test adaptor that #includes the c file. See Accessing static Data and Functions in Legacy C — Part 2.
The legacy code calls library functions that are not available on the test platform.
Create a set of test stubs for the production code library. Link with that library for development system tests. Make some of the test doubles spies, mocks, etc. JIT as needed.
A legacy code file has a few functions that make testing difficult. They may have hardware or OS dependencies.
Extract the problem functions declarations into a separate header file. Include it from the original file. Extract the problem function implementations into a separate source file. Create a replacement source file made up of test stubs for the problem functions. Link with the replacement for test.
If you try to use the linker for test double substitution, you cannot have the original code in the same executable. You need the production code in some tests.
Create a function pointer with the same signature as the problem function. By default initialize the pointer with the problem function. Change clients to call through the function pointer. Override the function in the tests where needed.
Make sure code runs warning free, if the caller does not see the declaration, C will assume it is a direct function call.
Use gcc linker wrapping. See option --wrap symbol
of this Stack Overflow article.
There is a limit to what can be done with the preprocessor and the linker.
Make careful changes to the code that enable testing or off-target compilation.
Published: April 06, 2014
A blank page can be very intimidating, even for a Test-driven developer. Where do we start? Write a test, right? Not always.
more...Here is a short interview with James about TDD and embedded software from the deliver:Agile conference last spring.
more...Do you have some time to do a simple programming problem in C or C++ for my research?
more...My long-time good friend (Uncle) Bob Martin and I have fun programming together firing tracer bullets for distributed water pressure measurement system.
more...You can find a recording of the webinar presentation James Grenning gave with Jama Software on this page (once it is posted): Agile for Embedded -- Overview and Pitfalls.
more...James is the author of Test-Driven Development for Embedded C.
Have you read Test-Driven Development for Embedded C? Please write a review at
Amazon
or
Good Reads
.