2011-04-03 63 views
11

假设我已经决定用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)); 

Yuck!一定有更好的方法。

有时候,我觉得DI和其他旨在提升模块性和维护简便性的类似模式实际上会妨碍代码的清晰性,并且使应将简单的编码变为无意义的抽象和复杂的间接代码变得复杂。

C中的大型软件项目(如Perl和Ruby)如何处理单元测试?

回答

3

如果您只需要DI进行单元测试,则可以使用链接器来完成。

我的意思是,函数B1 & B2在头中声明并被函数A使用,所以B函数的实现由链接器提供。你只需要为单元测试提供一个不同的C-File。这应该不是一个大问题,因为无论如何你可能都有自己的makefile用于单元测试。

如果在运行时需要动态依赖关系解析,则​​应使用函数指针的工厂模式(返回函数指针的函数),并在需要时将其从工厂拉出。工厂可以根据全球环境来决定返回什么功能。

+0

这是一个有趣的想法。我将不得不尝试。 – kirakun 2011-04-14 17:32:58

+0

你也可以用C编写面向对象程序,而不仅仅是程序。只需声明一个带有数据成员和函数的结构,将结构作为第一个参数(伪指针)。在c文件中实现,你已经创建了一个伪类。多态性和这样的事情是可能的,只是复杂的;) – sanosdole 2011-04-14 21:00:03

0

我喜欢这个问题。它在程序语言中有点棘手......但我认为你可以从OO世界借鉴一个想法,人们经常使用构造函数重载来处理一些DI工作。因此,例如,您将使用像往常一样设置所有依赖项的默认构造函数......但是另外还有另一个允许注入依赖项的构造函数。

既然你是程序性的......我想你可以使用函数重载来为你处理。此外,当您正在测试时,您只需在调用A ...时嘲笑B1 & B2,以便您可以简化DI用于此目的。换句话说,如果你真的只使用DI的单元测试,那么你就不必仅注入dependcies第一级依赖条件的整个树...

所以从你可能有...

int A(int input){ 
// create function point to b1 & b2 and call "return A(input, {pointer to b1},{pointer to b2})" 
} 

原谅我的伪代码,它一直以来我所做C.

2

你可以把依赖于C-结构,这将成为函数调用一个参数很长一段时间。在c中,这将类似于文件api,其中第一个参数始终是文件句柄

+0

我们在其他语言中做了类似的事情,有一个服务提供者,它具有各种依赖关系的实现。它具有额外的好处,可以减少对函数签名的更改并使重构更容易。 – Enno 2011-04-15 03:26:03

3

A只需要调用B1B2。它不需要知道关于C级别的任何事情。

为了测试A,您可以将B1B2的不同虚拟版本注入A

这将A从需要整体结构中分离出来,意味着您可以单独测试每个功能。

0

您可以在没有DI的情况下对B1,B2和A进行适当的单元测试。就像叶子函数一样,B1和B2有有效的输入和输出,并且你测试那些,和A一样.B1可以在内部使用C11和C12来帮助你完成它的单元测试,并不意味着它们必须被注入你不需要这种灵活性。

+0

但它如何规模?在规范中,'A'将覆盖'B1'和'B2'的功能,它们都涵盖了'C11','C12','C21'和'C22'的功能。 因此,所有叶函数'C11'等的测试都必须为它们上面的所有节点函数重复。现在,想象一个深层次的代码集。你可能会发现编写测试非常费力。 – kirakun 2011-04-14 17:32:20

+0

@Kirakun在实际情况下,A和B1和B2不一样,它增加了一些额外的功能。如果你有B1和B2的测试,你可以对代码的那部分有信心。对于你而言,你只需要测试你需要确信如何将B1和B2连接在一起。 – tddtrying 2011-04-14 22:20:35

+0

@tddtrying恕我直言*单元*测试(在上面的场景中)意味着测试'A' *而不在B级测试(即调用)函数,更不用说C级。 B级可能是有争议的,但想象一下'C11'连接到一个数据库。你不需要'A'(或'B1')的单元测试来调用实际的'C11'。相反应该调用某种模拟'C11'。 – TobiMcNamobi 2015-02-18 10:19:00