【问题标题】:Dependency Injection for Procedural Programming过程编程的依赖注入
【发布时间】:2011-04-03 05:28:26
【问题描述】:

假设我决定用 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 和其他旨在促进模块化和易于维护的类似模式实际上会妨碍代码的清晰性,并使应该直接编码为无意义的抽象和复杂的间接的复杂化。

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

【问题讨论】:

    标签: dependency-injection procedural-programming


    【解决方案1】:

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

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

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

    【讨论】:

    • 这是一个有趣的想法。我得试试看。
    • 您也可以用 C 编写面向对象,而不仅仅是程序。只需声明一个带有数据成员的结构和将结构作为第一个参数的函数(伪 this 指针)。在 c 文件中实现,您已经为自己创建了一个伪类。多态性和类似的事情是可能的,只是复杂;)
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 我们用其他语言做类似的事情,有一个服务提供者,其中实现了各种依赖项。它还有一个额外的好处,那就是减少对函数签名的更改,并使重构更容易。
      【解决方案4】:

      我喜欢这个问题。在过程语言中它变得有点棘手......但我认为你可以从 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 语言了。

      【讨论】:

        【解决方案5】:

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

        【讨论】:

        • 但它是如何扩展的?在规范中,A 将涵盖B1B2 的功能,这两个功能还涵盖C11C12C21C22 的功能。因此,对于叶函数C11 等的所有测试都必须针对它们上面的所有节点函数重复。现在,想象一组深层代码。您可能会发现编写测试非常费力。
        • @Kirakun 在实际情况下,A 与 B1 和 B2 不同,它添加了某种额外的功能。如果您对 B1 和 B2 进行了测试,您可以对代码的那一部分充满信心。对于 A,您只需测试您需要确信它如何将 B1 和 B2 联系在一起。
        • @tddtrying 恕我直言 unit 测试(在上面的场景中)意味着在 B 级测试 A 而不测试(即调用)函数,更不用说C级了。 B 级可能值得商榷,但想象一下C11 连接到数据库。您不希望A(或B1)的单元测试调用实际的C11。相反,应该调用某种模拟-C11
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多