【问题标题】:How do I mock objects without inheritance (in C)?如何在没有继承的情况下模拟对象(在 C 中)?
【发布时间】:2008-10-31 10:06:25
【问题描述】:

我们在工作中为我们的低级网络代码使用一个简单的对象模型,其中结构指针被传递给伪装成方法的函数。我继承了大部分代码,这些代码充其量是由具有合格 C/C++ 经验的顾问编写的,并且我花了很多个深夜试图将代码重构为类似于合理结构的东西。

现在我想对代码进行单元测试,但考虑到我们选择的对象模型,我不知道如何模拟对象。请看下面的例子:

示例头文件(foo.h):

#ifndef FOO_H_
#define FOO_H_

typedef struct Foo_s* Foo;
Foo foo_create(TcpSocket tcp_socket);
void foo_destroy(Foo foo);
int foo_transmit_command(Foo foo, enum Command command);

#endif /* FOO_H_ */

示例源(foo.c):

struct Foo_s {
    TcpSocket tcp_socket;
};

Foo foo_create(TcpSocket tcp_socket)
{   
    Foo foo = NULL;

    assert(tcp_socket != NULL);

    foo = malloc(sizeof(struct Foo_s));
    if (foo == NULL) {
        goto fail;
    }
    memset(foo, 0UL, sizeof(struct Foo_s));

    foo->tcp_socket = tcp_socket;

    return foo;

fail:
    foo_destroy(foo);
    return NULL;
}

void foo_destroy(Foo foo)
{
    if (foo != NULL) {
            tcp_socket_destroy(foo->tcp_socket);
            memset(foo, 0UL, sizeof(struct Foo_s));
            free(foo);
    }
}

int foo_transmit_command(Foo foo, enum Command command)
{
    size_t len = 0;
    struct FooCommandPacket foo_command_packet = {0};

    assert(foo != NULL);
    assert((Command_MIN <= command) && (command <= Command_MAX));

    /* Serialize command into foo_command_packet struct */
    ...

    len = tcp_socket_send(foo->tcp_socket, &foo_command_packet, sizeof(foo_command_packet));
    if (len < sizeof(foo_command_packet)) {
            return -1;
    }
    return 0;
}

在上面的示例中,我想模拟 TcpSocket 对象,以便可以将 "foo_transmit_command" 带入单元测试,但我不知道该怎么做这没有继承。我真的不想重新设计代码以使用 vtables,除非我真的必须这样做。也许有比嘲笑更好的方法?

我的测试经验主要来自C++,我有点害怕我可能会把自己画到这里。我非常感谢更有经验的测试人员的任何建议。

编辑:
就像 Richard Quirk 指出的那样,我想要覆盖的确实是对 "tcp_socket_send" 的调用,我更愿意在链接测试时不从库中删除真正的 tcp_socket_send 符号,因为它被调用通过同一二进制文件中的其他测试。

我开始认为这个问题没有明显的解决方案..

【问题讨论】:

    标签: c unit-testing mocking


    【解决方案1】:

    您可以使用宏将tcp_socket_send 重新定义为tcp_socket_send_moc,并链接到真实的tcp_socket_sendtcp_socket_send_moc 的虚拟实现。
    您需要仔细选择合适的位置:

    #define tcp_socket_send tcp_socket_send_moc
    

    【讨论】:

    • 我可以将它放在 TcpSocket 标头中并将其包含在 #ifdef TESTING .. #endif 中。好建议。
    • 你更了解你的代码,我只是想说明宏是一个整体,宏放置在错误的位置会导致问题非常难以调试
    【解决方案2】:

    看看TestDept: http://code.google.com/p/test-dept/

    这是一个开源项目,旨在提供替代实现的可能性,例如存根,函数,并且能够在运行时更改要使用的所述函数的实现。

    这一切都是通过修改项目主页上很好地描述的目标文件来完成的。

    【讨论】:

    • +1 - 这看起来是测试高级嵌入式代码的绝佳选择,尤其是那些在应用程序空间或基础设施空间中的代码。谢谢!
    【解决方案3】:

    或者,您可以使用 TestApe TestApe Unit testing for embedded software - 它可以做到,但请注意它仅适用于 C。 它会是这样的-->

    int mock_foo_transmit_command(Foo foo, enum Command command) {
      VALIDATE(foo, a);
      VALIDATE(command, b);
    }
    
    void test(void) {
      EXPECT_VALIDATE(foo_transmit_command, mock_foo_transmit_command);
      foo_transmit_command(a, b);
    }
    

    【讨论】:

      【解决方案4】:

      不确定你想要达到什么目的。

      您可以将所有 foo_* 函数作为函数指针成员添加到 struct Foo_s,但您仍然需要显式地将指针传递给您的对象,因为 C 中没有隐式 this。但它会给您封装和多态性。

      【讨论】:

      • 这将需要我重写大部分代码库“只是”以将代码放入单元测试中,我最好避免这样做。
      【解决方案5】:

      您使用的是什么操作系统?我相信您可以在 GNU/Linux 上使用 LD_PRELOAD 进行覆盖:This slide looks useful.

      【讨论】:

      • Linux 但在某些情况下依赖项都存在于同一个库中,因此如果我预加载另一个库,我也会覆盖我正在测试的对象。
      • 不,链接器只会在 LD_PRELOAD 中找到一个符号——tcp_socket_send——,其他的都将照常链接。
      【解决方案6】:

      使用宏来细化 tcp_socket_send 很好。但是模拟只返回一种行为。或者您需要在模拟函数中实现一些变量,并在每个测试用例之前进行不同的设置。 另一种方法是将 tcp_socket_send 更改为功能点。并针对不同的测试用例指向不同的模拟函数。

      【讨论】:

        【解决方案7】:

        补充伊利亚的答案。你可以这样做。

        #define tcp_socket_send tcp_socket_send_moc
        #include "your_source_code.c"
        
        int tcp_socket_send_moc(...)
        { ... }
        

        我使用将源文件包含到单元测试模块中的技术,以在创建单元测试时尽量减少对源文件的修改。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-05-17
          • 2022-08-02
          • 2015-11-25
          • 1970-01-01
          • 2012-08-05
          • 1970-01-01
          相关资源
          最近更新 更多