We’re working on adding significant new functionality to a legacy system. The budget doesn’t support a complete re-write. None of the legacy code has tests. The application domain is automotive testing. The particular problem at hand is controlling a throttle actuator (a simple robot that activates the throttle of a car while it’s being tested.)
The challenge
Legacy C source, 1600 lines. Many authors, 15+ years old. Complex if/else logic blocks with multiple clauses in conditionals. Several global variables. Throttle commands sent out serial port. Shared memory global variables via CPP macro interface.
How to add functionality in a TDD fashion for moving the robot smoothly?
The Solution
The approach we took preserved the interface for the legacy code, required simple enough changes that we were pretty confident we hadn’t broken anything (though we can only run the system to know this…), gave us a way forward to use TDD for new functionality, lets us run the new test suite without involving hardware, and only took 1.5 person hours to implement.
Where We Started
Legacy C source, 1600 lines. Many authors, 15+ years old. Function declarations at the top, implementations below. Complex if/else blocks with multiple clauses in conditional. Defines scattered in file. Several global variables. Throttle commands sent out serial port via SendCmd() function. Shared memory global variables via CPP macro interface. Main function of 100 lines with a while(1) loop.
Our Goal
Create new, testable implementations of functions which move the throttle. Customer wants smoother throttle movement using adaptive acceleration and velocity. Functions that do this must use and set global shared variables, and send bytes out the serial port. Tests should run without requiring other processes, or using hardware.
What We Did
1. Created SystemVariables class
Get and Set method for each system variable
|
|
#define ThrottleTeachMode shared_global_memory[offset];
|
replaced by
1 2 3 4 |
class SystemVariables {
int sv_ThrottleTeachMode();
void sv_ThrottleTeachMode(int mode);
};
|
|
|
if (ThrottleTeachMode) {
|
become
1 2 |
SystemVariables* sv = new SystemVariables();
if (sv->sv_ThrottleTeachMode()) {
|
2. Created Throttle.h
1 2 3 4 5 6 |
class SerialPort
SerialPort(SystemVariables* sv);
virtual void SendCmd();
class Throttle
Throttle(SystemVariables* sv, SerialPort* port);
|
3. Modified Throttle.c
1 2 3 4 5 6 7 8 9 |
global serial port object ref SerialPort* serialPort; change SendCmd() instances to serialPort->SendCmd() with editor global replace main() creates system variables object creates a SerialPort object, initializes global reference creates Throttle(sv, port) |
moved legacy functions-under-test to instance methods of Throttle class
moved a few utility functions used only by f-u-t to private methods of Throttle class
moved #defines from Throttle.c to Throttle.h
both legacy and Throttle class can access
allocate a Throttle object for main’s use
|
|
Throttle theThrottle(sv, port); |
replace invocation of “top” function in main loop with call to
1 2 |
theThrottle.process() process(); |
becomes
|
|
theThrottle.process(); |
4. Created Throttle.cpp
Instance methods to replace f-u-t
void process()
calls to other f-u-t “just work” (members of the class)
5. Created ThrottleTest.cpp
MockSystemVariables : public SystemVariables
override SVs used by f-u-t
MockSerialPort : public SerialPort
override SendCmd()
create Throttle object to test against, supplying mock objects
Throttle testThrottle(new MockSystemVariables, new MockSerialPort);
testThrottle.f-u-t();
assert(something about testThrottle);
assert(state of mock serial port);
test methods for new methods of Throttle following TDD practices

