我正在寻找一个很好的技术解决方案,在C中进行DI。
我已经在这里看到了一些DI问题,但我还没有看到任何实际的例子或具体的实现建议。
所以,假设我们有以下情况:
我们在C中有一组模块;我们想重构这些模块,这样我们就可以使用DI来运行单元测试等等。
每个模块实际上由一组c函数组成:
function();
模块相互依赖。即。通常你可能有一个调用,如:
int module1_doit(int x) {
int y = module2_dosomethingelse(x);
y += 2;
return(y);
}
为此,DI的正确方法是什么?
可能的解决办法似乎是:
- (1)对所有模块函数使用函数指针,并且在调用函数时执行以下操作(或类似操作):
int y = modules-〉module2-〉dossomethingelse(x);
- (2)使用相同的符号编译多个库(mock、std等),并在正确的实现中动态链接。
(2)看起来是正确的方法,但是很难配置,而且烦人的是迫使您为每个单元测试构建多个二进制文件。
(1)看起来它可能会工作,但在某些时候,你的DI控制器会陷入这样一种情况,你需要动态调用一个通用工厂函数(void ( factory)(...)),其中有许多其他模块需要在运行时注入?
在C语言中有没有更好的方法来实现这一点?
做这件事的“正确”方法是什么?
6条答案
按热度按时间bweufnob1#
你可以使用两种方法。你是否真的想要,正如雷夫所指出的,取决于你。
第一:在静态库中创建“动态”注入的方法。链接到库中,在测试过程中简单地替换它。瞧,方法被替换了。
第二:简单地提供基于预处理的编译时替换:
xeufq47z2#
下面是一个如何在C中完成依赖注入的例子(基本上是OP提到的选项(2)):
如果
get_todays_date()
在生产环境中执行,它应该总是返回准确的日期。如果
get_todays_date()
在测试环境中执行,我们让它返回一个固定的日期,这样就可以编写以下测试:get_todays_date
是person.h
文件的依赖项当您为生产环境编译代码时,链接包含
get_todays_date()
的真实的实现的转换单元(即,将get_todays_date.cc
文件包含到编译过程中)在编译代码进行测试时,不要链接对应于
get_todays_date.cc
的转换单元brvekthn3#
我不认为在C中使用DI有任何问题。
http://devmethodologies.blogspot.com/2012/07/dependency-injection.html
o4tp2gmn4#
我的结论是,在C语言中没有正确的方法来做这件事。它总是比其他语言更困难和乏味。然而,我认为重要的是不要为了单元测试而混淆你的代码。在C中将所有内容都变成函数指针听起来不错,但我认为这只会让代码在最后调试时变得可怕。
我最新的方法是让事情变得简单。我不改变C模块中的任何代码,除了文件顶部的一个小
#ifdef UNIT_TESTING
用于外部和内存分配跟踪。然后我将模块编译为删除所有依赖项,以便它无法链接。一旦我检查了未解决的符号,以确保它们是我想要的,我运行一个脚本来解析这些依赖项并为所有符号生成存根原型。这些都被转储到单元测试文件. YMMV中,具体取决于外部依赖项的复杂程度。如果我需要在一个示例中模拟一个依赖项,在另一个示例中使用真实的的依赖项,或者在另一个示例中使用它,那么我最终会为一个正在测试的模块使用三个单元测试模块。拥有多个二进制文件可能不是理想的,但这是C的唯一真正选择。尽管如此,它们都可以同时运行,所以这对我来说不是问题。
xu3bshqb5#
这是Ceedling的完美用例。
Ceedling是一个伞式项目,它将Unity和CMock(以及其他东西)结合在一起,可以自动化您所描述的许多工作。
一般来说,Ceedling/Unity/CMock是一组ruby脚本,可以扫描你的代码,并根据你的模块头文件自动生成mock,还有测试运行器,可以找到所有的测试,并生成运行它们的运行器。
为每个测试套件生成一个单独的测试运行器二进制文件,并根据您在测试套件实现中的请求链接适当的模拟和真实的实现。
我最初很犹豫是否要把ruby作为一个依赖项引入到我们的构建系统中进行测试,它看起来很复杂,很神奇,但是在尝试了一下并使用自动生成的模拟代码编写了一些测试之后,我被迷住了。
wqlqzqxt6#
这个问题有点晚了,但这是我最近工作的一个主题。
我见过的两种主要方法是使用函数指针,或者将所有依赖项移动到特定的C文件。
后者的一个很好的例子是FATFS.http://elm-chan.org/fsw/ff/en/appnote.html
fatfs的作者提供了大量的库函数,并将某些特定的依赖关系降级为库用户编写(例如串行外围接口函数)。
函数指针是另一个有用的工具,使用typedef有助于防止代码变得太难看。
以下是我的模数转换器(ADC)代码中的一些简化片段:
Foo的中断行为现在被注入ADC的中断服务例程。
您可以更进一步,向FOO_Initialize传递一个函数指针,这样所有依赖性问题都由应用程序管理。