Как мне издеваться над объектами без наследования (в C)? - PullRequest
6 голосов
/ 31 октября 2008

Мы используем простую объектную модель для нашего низкоуровневого сетевого кода в работе, где указатели на структуру передаются функциям, которые притворяются методами. Я унаследовал большую часть этого кода, который в лучшем случае был написан консультантами с опытом прохождения 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 ++, и я немного боюсь, что мог бы заглянуть сюда в угол. Буду очень признателен за рекомендации более опытных тестировщиков.

Edit:
Как отметил Ричард Квирк, это действительно вызов "tcp_socket_send" , который я хочу переопределить, и я бы предпочел сделать это без удаления реального символа tcp_socket_send из библиотеки при связывании теста, поскольку он вызывается другие тесты в том же двоичном файле.

Я начинаю думать, что нет очевидного решения этой проблемы ..

Ответы [ 7 ]

3 голосов
/ 31 октября 2008

Вы можете использовать макрос для переопределения tcp_socket_send в tcp_socket_send_moc и связывания с реальной tcp_socket_send и фиктивной реализацией для tcp_socket_send_moc.
вам нужно будет тщательно выбрать подходящее место для:

#define tcp_socket_send tcp_socket_send_moc
2 голосов
/ 10 июня 2009

Посмотрите на TestDept: http://code.google.com/p/test-dept/

Это проект с открытым исходным кодом, целью которого является предоставление возможности иметь альтернативные реализации, например, заглушки, функции и возможность изменения во время выполнения, какую реализацию указанной функции использовать.

Все это достигается путем искажения объектных файлов, что очень хорошо описано на домашней странице проекта.

1 голос
/ 01 декабря 2011

В качестве альтернативы, вы можете использовать TestApe TestApe Unit-тестирование для встроенного программного обеспечения - Это можно сделать, но учтите, что это только 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);
}
0 голосов
/ 03 ноября 2015

Добавить к ответу Ильи. Вы можете сделать это.

#define tcp_socket_send tcp_socket_send_moc
#include "your_source_code.c"

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

Я использую технику включения исходного файла в модуль модульного тестирования, чтобы минимизировать изменения в исходном файле при создании модульных тестов.

0 голосов
/ 09 декабря 2010

Используйте макрос для уточнения tcp_socket_send - это хорошо. Но макет возвращает только одно поведение. Или вам нужно внедрить некоторую переменную в функцию mock и настроить ее по-разному перед каждым тестом. Другой способ - изменить tcp_socket_send на точку функции. И указывает на разные фиктивные функции для разных тестовых случаев.

0 голосов
/ 31 октября 2008

Какую ОС вы используете? Я полагаю, что вы можете выполнить переопределение с LD_PRELOAD в GNU / Linux: Этот слайд выглядит полезным.

0 голосов
/ 31 октября 2008

Не уверен, чего ты хочешь достичь.

Вы можете добавить все функции foo_ * в качестве членов-указателей на struct Foo_s, но вам все равно нужно явно передать указатель на ваш объект, поскольку в C. нет неявного this, но это даст вам инкапсуляцию и полиморфизм. 1005 *

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...