12

假设我决定用 C 或任何其他过程编程语言编写一个大型应用程序。它具有如下所示的调用依赖函数:

A
|
+-------------+
|             |
B1            B2
|             |
+------+      +------+
|      |      |      |
C11    C12    C21    C22

显然,对叶子函数 C11、C12、C21 和 C22 进行单元测试非常容易:设置输入、调用函数、断言输出。

但是,为 B1、B2 和 A 启用良好的单元测试的正确策略是什么?

依赖注入是否会建议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,如何处理单元测试?

4

5 回答 5

3

A只需要调用B1and B2。它不需要了解 C 级别的任何内容。

您可以注入不同的虚拟版本的函数B1B2进行A测试A

A与需要整个结构隔离开来,意味着您可以单独测试每个功能。

于 2011-04-06T05:30:04.123 回答
3

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

我的意思是函数 B1 和 B2 在头文件中声明并由函数 A 使用,因此 B 函数的实现由链接器提供。您只需要为单元测试提供不同的 C 文件。这应该不是一个大问题,因为无论如何您可能都有自己的单元测试生成文件。

如果您需要在运行时进行动态依赖解析,您应该为函数指针使用工厂模式(返回函数指针的函数),并在需要时从工厂中提取它们。工厂可以根据全局上下文决定返回什么函数。

于 2011-04-14T06:21:39.820 回答
2

您可以将依赖项放到一个 c-struct 中,该 c-struct 将成为函数调用的一个参数。在 c 中,这类似于文件 api,其中第一个参数始终是文件句柄

于 2011-04-03T06:10:04.453 回答
0

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

既然你是程序性的......我认为你可以使用函数重载来为你处理这个问题。此外,当您进行测试时,您只需要在调用 A... 时模拟 B1 和 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 以来已经很久了。

于 2011-04-03T05:45:54.240 回答
0

您可以在没有 DI 的情况下对 B1、B2 和 A 进行适当的单元测试。就像叶函数一样,B1 和 B2 具有有效的输入和输出,您可以对它们进行测试,与 A 相同。B1 可以在内部使用 C11 和 C12 来帮助您完成其单元测试,但这并不意味着它们必须在案例中注入你不需要那种灵活性的地方。

于 2011-04-14T15:04:20.010 回答