I'm looking for a good technical solution to doing DI in C.
I have seen some of the DI questions here already, but I haven't seen one with any actual examples or concrete implementation suggestions.
So, lets say we have the following situation:
We have a set of modules in c; we want to refactor those modules so that we can use DI to run unit tests and so on.
Each module effectively consists of a set of c functions:
module_function(...);
Modules depend on each other. Ie. Typically you may have a call such as:
int module1_doit(int x) {
int y = module2_dosomethingelse(x);
y += 2;
return(y);
}
What is the correct approach to DI for this?
Possible solutions seem to be:
(1) Using function pointers for all module functions, and when invoking a function do this (or similar):
int y = modules->module2->dosomethingelse(x);
(2) Compile multiple libraries (mock, std, etc.) of with the same symbols and dynamically link in the correct implementation.
(2) seems to be the correct way of doing it, but is difficult to configure and annoyingly forces you to build multiple binaries for each unit test.
(1) Seems like it might work, but at some point your DI controller is going to get stuck in a situation where you need to dynamically invoke a generic factory func开发者_JAVA技巧tion (void ( factory) (...) say) with a number of other modules that need to be injected at runtime?
Is there another, better way of doing this in c?
What's the 'right' way of doing it?
I don't see any problem with using DI in C. See:
http://devmethodologies.blogspot.com/2012/07/dependency-injection.html
I've concluded that there is no 'right' way of doing this in C. It's always going to be more difficult and tedious than in other languages. I think it's important, however, not to obfuscate your code for the sake of unit tests, though. Making everything a function pointer in C may sound good, but I think it just makes the code horrific to debug in the end.
My latest approach has been to keep things simple. I don't change any code in C modules other than a small #ifdef UNIT_TESTING
at the top of a file for externing and memory allocation tracking. I then take the module and compile it with all dependencies removed so that it fails link. Once I've reviewed the unresolved symbols to make sure they are what I want, I run a script that parses these dependencies and generates stub prototypes for all the symbols. These all get dumped in the unit test file. YMMV depending on how complex your external dependencies are.
If I need to mock a dependency in one instance, use the real one in another, or stub it in yet another, then I end up with three unit test modules for the one module under test. Having multiple binaries may not be ideal, but it's the only real option with C. They all get run at the same time, though, so it's not really a problem for me.
This is a perfect use-case for Ceedling.
Ceedling is sort umbrella project that brings together (among other things) Unity and CMock, which together can automate a lot of the work you're describing.
In general Ceedling/Unity/CMock are a set of ruby scripts that scan through your code and auto-generate mocks based on your module header files, as well as test runners that find all the tests and makes runners that will run them.
A separate test runner binary is generated for each test suite, linking in the appropriate mock and real implementations as you request in your test suite implementation.
I was initially hesitant to bring in ruby as a dependency to our build system for testing, and it seemed like a lot of complexity and magic, but after trying it out and writing some tests using the auto-generated mocking code I was hooked.
A little late to the party on this but this has been a recent topic where I work.
The two main ways that I've seen it done is using function pointers, or moving all dependencies to a specific C file.
A good example of the later is FATFS. http://elm-chan.org/fsw/ff/en/appnote.html
The author of fatfs provides the bulk of the library functions and relegates certain specific dependencies for the user of the library to write (e.g. serial peripheral interface functions).
Function pointers are another useful tool, and using typedefs help to keep the code from getting too ugly.
Here's some simplified snippets from my Analog to Digital Converter (ADC) code:
typedef void (*adc_callback_t)(void);
bool ADC_CallBackSet(adc_callback_t callBack)
{
bool err = false;
if (NULL == ADC_callBack)
{
ADC_callBack = callBack;
}
else
{
err = true;
}
return err;
}
// When the ADC data is ready, this interrupt gets called
bool ADC_ISR(void)
{
// Clear the ADC interrupt flag
ADIF = 0;
// Call the callback function if set
if (NULL != ADC_callBack)
{
ADC_callBack();
}
return true; // handled
}
// Elsewhere
void FOO_Initialize(void)
{
ADC_CallBackSet(FOO_AdcCallback);
// Initialize other FOO stuff
}
void FOO_AdcCallback(void)
{
ADC_RESULT_T rawSample = ADC_ResultGet();
FOO_globalVar += rawSample;
}
Foo's interrupt behavior is now injected into the ADC's interrupt service routine.
You can take it a step further and pass a function pointer into FOO_Initialize so all dependency issues are managed by the application.
//dependency_injection.h
typedef void (*DI_Callback)(void)
typedef bool (*DI_CallbackSetter)(DI_Callback)
// foo.c
bool FOO_Initialize(DI_CallbackSetter CallbackSet)
{
bool err = CallbackSet(FOO_AdcCallback);
// Initialize other FOO stuff
return err;
}
There are two approaches that you can use. Whether you really want to or not, as Rafe is pointing out, are up to you.
First: Create the "dynamically" injected method in a static library. Link against the library and simply substitute it during tests. Voila, the method is replaced.
Second: Simply provide compile-time replacements based on preprocessing:
#ifndef YOUR_FLAG
/* normal method versions */
#else
/* changed method versions */
#endif
/* methods that have no substitute */
精彩评论