假设我决定用C或任何其他过程编程语言编写大型应用程序。它具有调用依赖关系的函数,如下所示:
A
|
+-------------+
| |
B1 B2
| |
+------+ +------+
| | | |
C11 C12 C21 C22
显然,对叶子函数C11,C12,C21和C22进行单元测试非常简单:设置输入,调用函数,断言输出。
但是,为B1,B2和A进行良好的单元测试的正确策略是什么?
Dependency Injection是否会建议B1
(以及B2
)声明如下:
// Declare B1 with dependency injection for invoking C11 and C12.
int B1(int input, int (*c11)(int), int(*c12)(int));
但如果我有多层呼叫,那么这种策略似乎不具备可扩展性。想象一下A
的声明是什么样的:
int A(int input, int (*b1)(int, int (*)(int), int(*)(int)),
int(*b2)(int, int (*)(int), int(*)(int)),
int (*c11)(int),
int (*c12)(int),
int (*c21)(int),
int (*c22)(int));
呸!必须有更好的方法。
有时,我觉得DI和其他旨在促进模块化和易维护性的类似模式实际上会妨碍代码清晰度,并使简单编码变成无意义的抽象和复杂的间接变得复杂。
C中的大型软件项目(如Perl和Ruby)如何处理单元测试?
答案 0 :(得分:3)
A
只需致电B1
和B2
。它不需要知道C级别的任何内容。
为了测试B1
,您可以将B2
和A
的不同虚拟版本注入A
。
这使A
不需要整个结构,这意味着您可以单独测试每个函数。
答案 1 :(得分:3)
如果您只需要DI进行单元测试,则可以使用链接器进行单元测试。
我的意思是功能B1& B2在头中声明并由函数A使用,因此B函数的实现由链接器提供。您只需要为单元测试提供不同的C-File。这应该不是一个大问题,因为无论如何你可能有自己的makefile用于单元测试。
如果在运行时需要动态依赖项解析,则应使用工厂模式(返回函数指针的函数)作为函数指针,并在需要时从工厂中取出它们。工厂可以根据全球情况决定返回什么功能。
答案 2 :(得分:2)
您可以将依赖项放入一个c-struct,它将成为函数调用的一个参数。在c中,这类似于文件api,其中第一个参数始终是文件句柄
答案 3 :(得分:0)
我喜欢这个问题。它在程序语言中有点棘手....但我认为你可以从OO世界借用一个想法,人们经常使用构造函数重载来处理一些DI工作。因此,例如,您将使用默认构造函数,像往常一样设置所有依赖项...但是还有另一个允许注入依赖项的构造函数。
既然你是程序性的...我认为你可以使用函数重载来为你处理这个问题。另外,当你进行测试时,你只需要模拟掉B1&调用A时的B2 ......所以你可以为此目的简化你的DI。换句话说,如果您真的只使用DI进行单元测试,那么您不必仅依靠第一级依赖注入整个依赖树...
所以从A你可能有......
int A(int input){
// create function point to b1 & b2 and call "return A(input, {pointer to b1},{pointer to b2})"
}
原谅我的伪代码,自从我做了C以来已经很久了。
答案 4 :(得分:0)
您可以在没有DI的情况下对B1,B2和A进行适当的单元测试。就像叶子功能一样,B1和B2具有有效的输入和输出,你测试它们,与A相同.B1内部可以使用C11和C12来帮助你完成单元测试并不意味着它们必须注入案例你不需要那种灵活性。