Модульный тест заглушки C вспомогательный метод - PullRequest
2 голосов
/ 03 февраля 2011

Я ищу способ заглушить вспомогательный метод, расположенный в том же C-файле.Есть ли способ сделать это без изменения исходного файла?Я думал о том, как использовать #define для замены метода b методом b_stub, но я думаю, что в итоге это переименует метод b

Вот пример использования:

#include "file.h"

a(){
    b();
}

b(){
}

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

Спасибо.

Ответы [ 4 ]

3 голосов
/ 03 февраля 2011

Я не уверен, что полностью понимаю ваш вопрос.

Если вы хотите вызвать процедуру, отличную от b, вы можете сделать это во время компиляции как:

a() {
#ifdef USE_STUB
    b_stub();
#else
    b();
#endif
}

Или, если вы всегда хотите вызвать b, но хотите, чтобы b вел себя по-другому, вы можете сделать это во время компиляции как:

a() {
    b():
}
b() {
#ifdef USE_STUB
    printf( "I am in the stub version of b()\n" );
#else
    printf( "I am in the real version of b()\n" );
#endif
}

Или вы можете делать подобные вещи во время выполнения с помощью (для простоты показано здесь с глобальной переменной):

a() {
    extern int _use_stub;
    if( _use_stub ) {
        b_stub();
    } else {
        b();
    }
}

или

a() {
    b();
}
b() {
    extern int _use_stub;
    if( _use_stub ) {
        printf( "This is the stub code\n" );
    } else {
        printf( "This is the real code\n" );
    }
}

С примерами времени компиляции вы можете переключаться назад и вперед, изменяя заголовочный файл или определение Makefile. С примерами выполнения вы можете переключаться вперед и назад с помощью параметра командной строки, переменной среды, панели пользовательских настроек или чего-либо еще.

1 голос
/ 03 декабря 2014

Я нашел решение, которое работает для меня, может быть, оно вам тоже поможет.

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

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

Следующий пример находился под сильным влиянием следующего:

  1. http://eradman.com/posts/tdd-in-c.html
  2. http://locklessinc.com/articles/mocking/
  3. http://www.embedded.com/design/programming-languages-and-tools/4007177/2/Doing-C-code-unit-testing-on-a-shoestring-Part-1-The-basics-and-the-tools

MUT обозначает тестируемый модуль в этом примере.

Предположим, у вас есть четыре файла: mut.h, mut.c, test_mut.h, test_mut.c. Предположим также, что вы можете определить символ UNIT_TEST при его создании.

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

Итак, давайте начнем с версии mut.c

#include <cstdbool>
#include "mut.h"

static bool foo(int baz);
static bool bar(int baz);

static bool foo(int baz)
{
    bool ret = false;

    if(bar(baz))
    {
        //do something
        ret = true;
    }
    else
    {
        ret = false;
    }
    return ret;
}

static bool bar(int baz)
{
    //Black box mystery / Don't care
}

Давайте предположим, что мы уже протестировали блок. Работает нормально. Теперь мы хотим протестировать foo, но нам не хочется настраивать все, что нам нужно для того, чтобы бар работал правильно. Таким образом, мы должны заглушить бар.

Итак, давайте добавим новый заголовок test_mut.h. Среди прочего, вы должны иметь следующее в test_mut.h

#ifdef UNIT_TEST
...

//Convert every static definition into an extern declaration.
#define static extern

bool bar_mock_true (int baz);
bool bar_mock_false(int baz);
bool bar_real      (int baz);

extern bool(*bar_ptr)(int baz);
#define bar bar_ptr

...
#endif

Итак, как вы можете видеть, мы определили новый указатель функции, который теперь может указывать на наши заглушки / насмешки или на нашу реальную функцию бара. Этот заголовок также будет включен в test_mut.c, так что функции-заглушки теперь могут быть определены внутри test_mut.c - их не нужно включать в mut.c, загромождая его.

Теперь давайте сделаем это полезным, нам нужно немного изменить mut.c

mut.c теперь должен будет включать "test_mut.h", стандартное объявление bar () должно быть отключено во время модульного тестирования, и нам нужно изменить определение функции на bar_real ()

...
#include "test_mut.h"
...
#ifdef UNIT_TEST
static bool bar(int baz);
#endif

...

#ifdef UNIT_TEST
static bool bar_real(int baz)
#else
static bool bar(int baz)
#endif
{
    //Black box mystery / Don't care
}

Каждая функция, которую вам нужно заглушить, будет нуждаться в аналогичных #ifdefs и переименовании вокруг объявления и определения. Таким образом, ваш тестируемый код, к сожалению, будет немного загроможден.

Теперь test_mut.c может использовать ваш код следующим образом:

#include <cstdbool>
#include "test_mut.h"

...

UT_STATIC void test_foo(void)
{
    int baz = 0;
    extern bool foo(int baz);
    //Test Case A
    bar_ptr = bar_mock_true;
    TEST_ASSERT(foo(baz), "Condition A");

    //Test Case B
    bar_ptr = bar_mock_false;
    TEST_ASSERT(!foo(baz), "Condition B");
}

bool bar_mock_true(int baz)
{
    return true;
}

bool bar_mock_false(int baz)
{
    return false;
}

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

.. Тестовые прогоны: 2

mut.c

#include <cstdbool>

#include "mut.h"
#include "test_mut.h"

static bool foo(int baz);

#ifndef UNIT_TEST
static bool bar(int baz);
#endif

static bool foo(int baz)
{
    bool ret = false;

    if(bar(baz))
    {
        //do something
        ret = true;
    }
    else
    {
        ret = false;
    }
    return ret;
}

#ifdef UNIT_TEST
static bool bar_real(int baz)
#else
static bool bar(int baz)
#endif
{
    //Black box mystery / Don't care
}

test_mut.h

#ifdef UNIT_TEST
#ifndef _TEST_MUT_H
#define _TEST_MUT_H

//Handle to your test runner
void test_mut(void);

//Track the number of test cases that have executed
extern int tests_run;

//Convert every static definitions into extern declarations.
#define static extern

//An alternate definition of static for the test barness to use
#define UT_STATIC static

bool bar_mock_true   (int baz);
bool bar_mock_false  (int baz);
bool bar_real        (int baz);

extern bool(*bar_ptr)(int baz);

#define bar bar_ptr

//Test Macros
#define TEST_FAIL(name)                                                           \
do                                                                                \
{                                                                                 \
    printf("\nTest \"%s\" failed in %s() line %d\n", (name), __func__, __LINE__); \
} while(0)

#define TEST_ASSERT(test_true,test_name)                                          \
do                                                                                \
{                                                                                 \
    tests_run++;                                                                  \
    if(!(test_true))                                                              \
    {                                                                             \
        TEST_FAIL(test_name);                                                     \
    }                                                                             \
    else                                                                          \
    {                                                                             \
        printf(".");                                                              \
    }                                                                             \
} while(0)

//... Insert any other macro instrumentation you may need...

#endif // _TEST_MUT_H
#endif // UNIT_TEST

test_mut.c

#ifdef UNIT_TEST

#include <cstdbool>
#include <cstdio>
#include "test_mut.h"
#include "mut.h"

UT_STATIC void test_foo(void);

int tests_run = 0;

inline UT_STATIC void test_report(void);

void test_mut(void) {
    //call your setup function(s)
    test_foo();
    //call your teardown function(s)

    test_report();
}

inline UT_STATIC void test_report(void)
{
    printf("\nTests Run: %d\n", tests_run);
}

void main(void)
{
    test_mut();
}

//Setup the function pointer for bar, by default it will point to the real
//bar function, and not a stub.
bool(*bar_ptr)(int baz) = bar_real;

UT_STATIC void test_foo(void)
{
    int baz = 0;
    extern bool foo(int baz);

    //Test Case A
    bar_ptr = bar_mock_true;
    TEST_ASSERT(foo(baz), "Condition A");

    //Test Case B
    bar_ptr = bar_mock_false;
    TEST_ASSERT(!foo(baz), "Condition B");
}

bool bar_mock_true(int baz)
{
    return true;
}

bool bar_mock_false(int baz)
{
    return false;
}

#endif
0 голосов
/ 03 февраля 2011

Вы должны изменить источник, но не сильно.

Попробуйте это

#define b() stub_b()

a(){
    b(); 
   }  

(b)()
{

}

Теперь вызовы метода b () будут заменены на stub_b (), а определение b () останется неизменным.:)

0 голосов
/ 03 февраля 2011

Если это в коде платформы, а не в коде конечного пользователя, то вы можете сделать что-то вроде этого

#ifndef UNIT_TEST_FUNC_B
b()
{
}
#endif

и теперь, когда вы хотите запустить модульный тест на B, вы определяете UNIT_TEST_FUNC_B и включаете заглушкукод в отдельном модуле

или если вы хотите сохранить тестовый код в том же модуле, вы делаете это

#ifndef UNIT_TEST_FUNC_B
b()
{
}
#else
b()
{
// test code goes here
}
#endif

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

...