6

我们在工作中为我们的低级网络代码使用一个简单的对象模型,其中结构指针被传递给伪装成方法的函数。我继承了大部分代码,这些代码充其量是由具有合格 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 符号,因为它是由其他测试调用的相同的二进制。

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

4

7 回答 7

3

您可以使用宏重新定义并链接tcp_socket_send到. 您需要仔细选择合适的位置:tcp_socket_send_moctcp_socket_sendtcp_socket_send_moc

#define tcp_socket_send tcp_socket_send_moc
于 2008-10-31T12:02:44.633 回答
2

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

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

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

于 2009-06-10T14:31:45.367 回答
1

或者,您可以对嵌入式软件使用 TestApe TestApe 单元测试- 它可以做到,但请注意它仅限于 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);
}
于 2011-12-01T17:55:44.360 回答
0

不确定你想达到什么目标。

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

于 2008-10-31T10:42:40.337 回答
0

您使用的是什么操作系统?我相信您可以在 GNU/Linux 上使用 LD_PRELOAD 进行覆盖:这张幻灯片看起来很有用。

于 2008-10-31T11:39:39.630 回答
0

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

于 2010-12-09T09:54:59.957 回答
0

添加到伊利亚的答案。你可以这样做。

#define tcp_socket_send tcp_socket_send_moc
#include "your_source_code.c"

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

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

于 2015-11-03T00:22:51.867 回答