Similar to AudioDroid's answer, you can use different headers included by one "facade header":
The include approach (Test double)
portconfig.h a header to include the data - depending on what target you need (or if you are using it for testing)
#ifndef PORTCONFIG_H
#define PORTCONFIG_H
#ifdef TARGET_A
# include <portconfig_target_a.h> /* Use the config for target A */
#elif defined TARGET_B
# include <portconfig_target_b.h> /* Use the config for target B */
#elif defined TEST
# include <portconfig_testing.h> /* Use this instead for testing */
#else
# error "Not supported!" /* Just in case ... */
#endif
#endif /* PORTCONFIG_H */
Each of these header files contains these "defines" as they are required by the target, eg.
portconfig_target_a.h
...
#define PORTA_CONFIG (*(volatile unsigned int*) (0x1000))
...
or
portconfig_testing.h
...
volatile unsigned int PORTA_CONFIG;
...
This requires a #ifdef
only at one central place and therefore has less maintenance effort. Also there's no difference in target / testing code usage, #include <portconfig.h>
is used in all cases.
The abstraction approach
Instead of using PORTA_CONFIG
directly, you can also abstract it into an function / macro. For testing you can mock these.
typedef IOAddress ...
IOData IOData ...
void writePort(IOAddress addr, IOData data);
IOData readPort(IOAddress);
This has the benefit of abstraction and is very useful for testing.
There's an implementation and some great examples in this repository, especially code
(MockIO, header, implementation, example test) - using CppUTest.
See also