如何在没有继承的情况下模拟对象(在C中)?

时间:2008-10-31 10:06:25

标签: c unit-testing mocking

我们使用一个简单的对象模型来处理我们的低级网络代码,其中struct指针被传递给假装是方法的函数。我继承了大部分代码,这些代码是由具有可通行的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”,但我不知道该如何去做这没有继承。除非我真的需要,否则我真的不想重新设计使用vtable的代码。也许有更好的方法比嘲笑?

我的测试经验主要来自C ++,我有点担心我可能会把自己描绘成一个角落。我非常感谢更有经验的测试人员的任何建议。

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

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

7 个答案:

答案 0 :(得分:3)

您可以使用宏将tcp_socket_send重新定义为tcp_socket_send_moc,并与tcp_socket_send的真实tcp_socket_send_moc和虚拟实施相关联。
你需要仔细选择合适的地方:

#define tcp_socket_send tcp_socket_send_moc

答案 1 :(得分:2)

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

这是一个开源项目,旨在提供替代实施的可能性,例如:存根,功能和能够在运行时改变所使用的所述功能的实现。

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

答案 2 :(得分:1)

或者,您可以使用TestApe TestApe Unit testing for embedded software - 它可以执行此操作,但请注意它仅为C. 它会像这样 - &gt;

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);
}

答案 3 :(得分:0)

不确定你想要达到的目标。

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

添加到Ilya的答案。你可以这样做。

#define tcp_socket_send tcp_socket_send_moc
#include "your_source_code.c"

int tcp_socket_send_moc(...)
{ ... }

我使用将源文件包含到单元测试模块中的技术来最小化创建单元测试时源文件中的修改。