FFF-это микрофрамочная работа для создания поддельных функций C для тестов. Потому что жизнь слишком коротка, чтобы тратить время на написание фальшивых функций для тестирования.
Чтобы запустить все тесты и образцы приложений, просто позвоните $ buildandtest . Этот скрипт приведет к Cmake со следующим:
cmake -B build -DFFF_GENERATE=ON -DFFF_UNIT_TESTING=ON
cmake --build build
ctest --test-dir build --output-on-failureСкажем, вы тестируете встроенный пользовательский интерфейс, и у вас есть функция, которую вы хотите создать подделку:
// UI.c
...
void DISPLAY_init ();
...Вот как вы бы определили поддельную функцию для этого в своем тестовом наборе:
// test.c(pp)
#include "fff.h"
DEFINE_FFF_GLOBALS ;
FAKE_VOID_FUNC ( DISPLAY_init );И модульный тест может выглядеть примерно так:
TEST_F ( GreeterTests , init_initialises_display )
{
UI_init ();
ASSERT_EQ ( DISPLAY_init_fake . call_count , 1 );
} Так что же случилось здесь? Первое, что нужно отметить, это то, что фреймворк только заголовок, все, что вам нужно сделать, чтобы использовать его, это загрузка fff.h и включить в свой тестовый набор.
Магия в FAKE_VOID_FUNC . Это расширяет макрос, который определяет функцию, возвращающую void , которая имеет нулевые аргументы. Он также определяет структуру "function_name"_fake , который содержит всю информацию о поддельной. Например, DISPLAY_init_fake.call_count увеличивается каждый раз, когда вызывается фальшивая функция.
Под капотом он генерирует структуру, которая выглядит так:
typedef struct DISPLAY_init_Fake {
unsigned int call_count ;
unsigned int arg_history_len ;
unsigned int arg_histories_dropped ;
void ( * custom_fake )();
} DISPLAY_init_Fake ;
DISPLAY_init_Fake DISPLAY_init_fake ;Хорошо, достаточно с примерами игрушек. Как насчет фальсификации функций с аргументами?
// UI.c
...
void DISPLAY_output ( char * message );
...Вот как вы бы определили поддельную функцию для этого в своем тестовом наборе:
FAKE_VOID_FUNC ( DISPLAY_output , char * );И модульный тест может выглядеть примерно так:
TEST_F ( UITests , write_line_outputs_lines_to_display )
{
char msg [] = "helloworld" ;
UI_write_line ( msg );
ASSERT_EQ ( DISPLAY_output_fake . call_count , 1 );
ASSERT_EQ ( strncmp ( DISPLAY_output_fake . arg0_val , msg , 26 ), 0 );
} Здесь больше нет магии, FAKE_VOID_FUNC работает, как в предыдущем примере. Количество аргументов, которые принимает функция, рассчитывается, и макро -аргументы, следующие за именем функции, определяют тип аргумента (указатель ChAR в этом примере).
Переменная создается для каждого аргумента в форме "function_name"fake.argN_val
Если вы хотите определить фальшивую функцию, которая возвращает значение, вы должны использовать макрос FAKE_VALUE_FUNC . Например:
// UI.c
...
unsigned int DISPLAY_get_line_capacity ();
unsigned int DISPLAY_get_line_insert_index ();
...Вот как вы бы определили поддельные функции для них в своем тестовом наборе:
FAKE_VALUE_FUNC ( unsigned int , DISPLAY_get_line_capacity );
FAKE_VALUE_FUNC ( unsigned int , DISPLAY_get_line_insert_index );И модульный тест может выглядеть примерно так:
TEST_F ( UITests , when_empty_lines_write_line_doesnt_clear_screen )
{
// given
DISPLAY_get_line_insert_index_fake . return_val = 1 ;
char msg [] = "helloworld" ;
// when
UI_write_line ( msg );
// then
ASSERT_EQ ( DISPLAY_clear_fake . call_count , 0 );
}Конечно, вы можете смешать и сопоставить эти макросы, чтобы определить функцию значения с аргументами, например, с фальшивой:
double pow ( double base , double exponent );Вы бы использовали такой синтаксис, как это:
FAKE_VALUE_FUNC ( double , pow , double , double );Хорошие тесты - это изолированные тесты, поэтому важно сбросить подделки для каждого модульного теста. Все подделки имеют функцию сброса, чтобы сбросить свои аргументы и количество вызовов. Хорошая практика - вызвать функцию сброса для всех подделок в функции настройки вашего тестового набора.
void setup ()
{
// Register resets
RESET_FAKE ( DISPLAY_init );
RESET_FAKE ( DISPLAY_clear );
RESET_FAKE ( DISPLAY_output_message );
RESET_FAKE ( DISPLAY_get_line_capacity );
RESET_FAKE ( DISPLAY_get_line_insert_index );
}Вы можете определить макрос, чтобы сделать это:
/* List of fakes used by this unit tester */
#define FFF_FAKES_LIST ( FAKE )
FAKE(DISPLAY_init)
FAKE(DISPLAY_clear)
FAKE(DISPLAY_output_message)
FAKE(DISPLAY_get_line_capacity)
FAKE(DISPLAY_get_line_insert_index)
void setup ()
{
/* Register resets */
FFF_FAKES_LIST ( RESET_FAKE );
/* reset common FFF internal structures */
FFF_RESET_HISTORY ();
}Скажите, что вы хотите проверить, что функция вызывает функцию, а затем функционировать, затем снова, как бы вы это сделали? Ну, FFF поддерживает историю вызовов, чтобы легко утвердить эти ожидания.
Вот как это работает:
FAKE_VOID_FUNC ( voidfunc2 , char , char );
FAKE_VALUE_FUNC ( long , longfunc0 );
TEST_F ( FFFTestSuite , calls_in_correct_order )
{
longfunc0 ();
voidfunc2 ();
longfunc0 ();
ASSERT_EQ ( fff . call_history [ 0 ], ( void * ) longfunc0 );
ASSERT_EQ ( fff . call_history [ 1 ], ( void * ) voidfunc2 );
ASSERT_EQ ( fff . call_history [ 2 ], ( void * ) longfunc0 );
} Они сбрасываются, вызывая FFF_RESET_HISTORY();
Фреймворк по умолчанию хранит аргументы для последних десяти вызовов, сделанных под фальшивой функцией.
TEST_F ( FFFTestSuite , when_fake_func_called_then_arguments_captured_in_history )
{
voidfunc2 ( 'g' , 'h' );
voidfunc2 ( 'i' , 'j' );
ASSERT_EQ ( 'g' , voidfunc2_fake . arg0_history [ 0 ]);
ASSERT_EQ ( 'h' , voidfunc2_fake . arg1_history [ 0 ]);
ASSERT_EQ ( 'i' , voidfunc2_fake . arg0_history [ 1 ]);
ASSERT_EQ ( 'j' , voidfunc2_fake . arg1_history [ 1 ]);
}Есть два способа выяснить, были ли вызовы вызовы. Первый - проверить счетчик сброшенных историй:
TEST_F ( FFFTestSuite , when_fake_func_called_max_times_plus_one_then_one_argument_history_dropped )
{
int i ;
for ( i = 0 ; i < 10 ; i ++ )
{
voidfunc2 ( '1' + i , '2' + i );
}
voidfunc2 ( '1' , '2' );
ASSERT_EQ ( 1u , voidfunc2_fake . arg_histories_dropped );
}Другой должен проверить, больше ли количество вызовов, чем размер истории:
ASSERT ( voidfunc2_fake . arg_history_len < voidfunc2_fake . call_count ); История аргументов для фальшивой функции сбрасывается, когда вызывается функция RESET_FAKE
Если вы хотите контролировать, сколько вызовов для захвата для истории аргументов вы можете переопределить по умолчанию, определив его, прежде чем включить fff.h
// Want to keep the argument history for 13 calls
#define FFF_ARG_HISTORY_LEN 13
// Want to keep the call sequence history for 17 function calls
#define FFF_CALL_HISTORY_LEN 17
#include "../fff.h" Часто при тестировании мы хотели бы проверить поведение последовательности событий вызова функций. Один из способов сделать это с помощью FFF - указать последовательность возвращающих значений с поддельной функцией. Вероятно, это легче описать с помощью примера:
// faking "long longfunc();"
FAKE_VALUE_FUNC ( long , longfunc0 );
TEST_F ( FFFTestSuite , return_value_sequences_exhausted )
{
long myReturnVals [ 3 ] = { 3 , 7 , 9 };
SET_RETURN_SEQ ( longfunc0 , myReturnVals , 3 );
ASSERT_EQ ( myReturnVals [ 0 ], longfunc0 ());
ASSERT_EQ ( myReturnVals [ 1 ], longfunc0 ());
ASSERT_EQ ( myReturnVals [ 2 ], longfunc0 ());
ASSERT_EQ ( myReturnVals [ 2 ], longfunc0 ());
ASSERT_EQ ( myReturnVals [ 2 ], longfunc0 ());
} Указав последовательность возвращаемого значения, используя макрос SET_RETURN_SEQ , подделка вернет значения, указанные в массиве параметров в последовательности. Когда достигнут конец последовательности, подделка продолжит возвращать последнее значение в последовательности на неопределенный срок.
Вы можете указать свою собственную функцию, чтобы обеспечить возвратное значение для подделки. Это делается путем установки члена custom_fake из подделки. Вот пример:
#define MEANING_OF_LIFE 42
long my_custom_value_fake ( void )
{
return MEANING_OF_LIFE ;
}
TEST_F ( FFFTestSuite , when_value_custom_fake_called_THEN_it_returns_custom_return_value )
{
longfunc0_fake . custom_fake = my_custom_value_fake ;
long retval = longfunc0 ();
ASSERT_EQ ( MEANING_OF_LIFE , retval );
} Скажем, у вас есть функция с параметром OUT, и вы хотите, чтобы у него было другое поведение на первых трех вызовах, например: установите значение «x» для параметра OUT на первом вызове, значение «y» для параметра OUT на втором вызове и значение «Z» для параметра OUT на третьем вызове. Вы можете указать последовательность пользовательских функций для не-вариадической функции, используя макрос SET_CUSTOM_FAKE_SEQ . Вот пример:
void voidfunc1outparam_custom_fake1 ( char * a )
{
* a = 'x' ;
}
void voidfunc1outparam_custom_fake2 ( char * a )
{
* a = 'y' ;
}
void voidfunc1outparam_custom_fake3 ( char * a )
{
* a = 'z' ;
}
TEST_F ( FFFTestSuite , custom_fake_sequence_not_exausthed )
{
void ( * custom_fakes [])( char * ) = { voidfunc1outparam_custom_fake1 ,
voidfunc1outparam_custom_fake2 ,
voidfunc1outparam_custom_fake3 };
char a = 'a' ;
SET_CUSTOM_FAKE_SEQ ( voidfunc1outparam , custom_fakes , 3 );
voidfunc1outparam ( & a );
ASSERT_EQ ( 'x' , a );
voidfunc1outparam ( & a );
ASSERT_EQ ( 'y' , a );
voidfunc1outparam ( & a );
ASSERT_EQ ( 'z' , a );
} Подделка вызовет ваши пользовательские функции в порядке, указанном в макросе SET_CUSTOM_FAKE_SEQ . Когда достигнут последняя индивидуальная подделка, подделка будет продолжать называть последнюю индивидуальную подделку в последовательности. Этот макрос работает так же, как макрос SET_RETURN_SEQ .
Скажем, у вас есть две функции F1 и F2. F2 должен быть вызван для выпуска некоторого ресурса, выделенного F1, но только в тех случаях, когда F1 возвращает ноль. F1 может быть pthread_mutex_trylock, а F2 могут быть pthread_mutex_unlock. FFF сохранит историю возвращаемых значений, поэтому это можно легко проверить, даже если вы используете последовательность пользовательских подделок. Вот простой пример:
TEST_F(FFFTestSuite, return_value_sequence_saved_in_history)
{
long myReturnVals[3] = { 3, 7, 9 };
SET_RETURN_SEQ(longfunc0, myReturnVals, 3);
longfunc0();
longfunc0();
longfunc0();
ASSERT_EQ(myReturnVals[0], longfunc0_fake.return_val_history[0]);
ASSERT_EQ(myReturnVals[1], longfunc0_fake.return_val_history[1]);
ASSERT_EQ(myReturnVals[2], longfunc0_fake.return_val_history[2]);
}
Вы получаете доступ к возвращенным значениям в поле return_val_history .
Вы можете фальшивые вариальные функции, используя макросы FAKE_VALUE_FUNC_VARARG и FAKE_VOID_FUNC_VARARG . Например:
FAKE_VALUE_FUNC_VARARG(int, fprintf, FILE *, const char*, ...);
Чтобы получить доступ к вариальным параметрам из пользовательской поддельной функции, объявьте параметр va_list . Например, пользовательская подделка для fprintf() может вызвать настоящий fprintf() таким образом:
int fprintf_custom(FILE *stream, const char *format, va_list ap) {
if (fprintf0_fake.return_val < 0) // should we fail?
return fprintf0_fake.return_val;
return vfprintf(stream, format, ap);
}
Так же, как делегаты возврата значения, вы также можете указать последовательности для вариальных функций с помощью SET_CUSTOM_FAKE_SEQ . Смотрите тестовые файлы для примеров.
FFF обладает ограниченной возможностью для обеспечения спецификации визуальных конвенций Microsoft C/C ++, но эта поддержка должна быть включена при генерации файла заголовка fff.h
ruby fakegen.rb --with-calling-conventions > fff.h Обеспечивая эту поддержку, все фальшивые фальшивые функции FFF потребуют спецификации вызовой соглашения, например, __cdecl для каждого значения или пустого подделки.
Вот несколько основных примеров: обратите внимание, что размещение указанной конвенции о вызове отличается в зависимости от того, является ли фальшивая функция void или value.
FAKE_VOID_FUNC ( __cdecl , voidfunc1 , int );
FAKE_VALUE_FUNC ( long , __cdecl , longfunc0 );Основным механизмом, который FFF предоставляет вам в этом случае, является поле Custom_fake, описанное в примере Degerate Extural Value .
Вам необходимо создать пользовательскую функцию (например, gettime_custom_fake), чтобы необязательно произвести выход с использованием вспомогательной переменной (например, gettime_custom_now), чтобы получить этот вывод. Тогда какое -то творчество, чтобы связать все это вместе. Наиболее важной частью (IMHO) является поддержание читаемого и обслуживания тестового примера.
В случае, если ваш проект использует компилятор C, который поддерживает вложенные функции (например, GCC) или при использовании Lambdas C ++ вы можете даже объединить все это в одной функции модульного тестирования, чтобы вы могли легко контролировать все детали теста.
#include <functional>
/* Configure FFF to use std::function, which enables capturing lambdas */
#define CUSTOM_FFF_FUNCTION_TEMPLATE ( RETURN , FUNCNAME , ...)
std::function<RETURN (__VA_ARGS__)> FUNCNAME
#include "fff.h"
/* The time structure */
typedef struct {
int hour , min ;
} Time ;
/* Our fake function */
FAKE_VOID_FUNC ( getTime , Time * );
/* A test using the getTime fake function */
TEST_F ( FFFTestSuite , when_value_custom_fake_called_THEN_it_returns_custom_output )
{
Time t ;
Time getTime_custom_now = {
. hour = 13 ,
. min = 05 ,
};
getTime_fake . custom_fake = [ getTime_custom_now ]( Time * now ) {
* now = getTime_custom_now ;
};
/* when getTime is called */
getTime ( & t );
/* then the specific time must be produced */
ASSERT_EQ ( t . hour , 13 );
ASSERT_EQ ( t . min , 05 );
}Использование функций FFF для загрязнения, которые имеют параметр указателя функции, может вызвать проблемы при попытке их загрязнить. Здесь представлен пример, как справиться с этой ситуацией.
Если вам нужно загрязнять функцию, которая имеет параметр указателя функции, например, что -то вроде:
/* timer.h */
typedef int timer_handle ;
extern int timer_start ( timer_handle handle , long delay , void ( * cb_function ) ( int arg ), int arg ); Затем создание подделки, подобного ниже, ужасно потерпит неудачу при попытке компиляции, потому что макрос FFF будет внутренне расширяться в незаконную переменную int (*)(int) arg2_val .
/* The fake, attempt one */
FAKE_VALUE_FUNC ( int ,
timer_start ,
timer_handle ,
long ,
void ( * ) ( int argument ),
int );Решение этой проблемы состоит в том, чтобы создать мостовой тип, который нуждается только для того, чтобы быть видимым в тестере модуля. Подделка будет использовать этот промежуточный тип. Таким образом, компилятор не будет жаловаться, потому что типы совпадают.
/* Additional type needed to be able to use callback in fff */
typedef void ( * timer_cb ) ( int argument );
/* The fake, attempt two */
FAKE_VALUE_FUNC ( int ,
timer_start ,
timer_handle ,
long ,
timer_cb ,
int );Вот несколько идей, как создать тестовый пример с обратными вызовами.
/* Unit test */
TEST_F ( FFFTestSuite , test_fake_with_function_pointer )
{
int cb_timeout_called = 0 ;
int result = 0 ;
void cb_timeout ( int argument )
{
cb_timeout_called ++ ;
}
int timer_start_custom_fake ( timer_handle handle ,
long delay ,
void ( * cb_function ) ( int arg ),
int arg )
{
if ( cb_function ) cb_function ( arg );
return timer_start_fake . return_val ;
}
/* given the custom fake for timer_start */
timer_start_fake . return_val = 33 ;
timer_start_fake . custom_fake = timer_start_custom_fake ;
/* when timer_start is called
* (actually you would call your own function-under-test
* that would then call the fake function)
*/
result = timer_start ( 10 , 100 , cb_timeout , 55 );
/* then the timer_start fake must have been called correctly */
ASSERT_EQ ( result , 33 );
ASSERT_EQ ( timer_start_fake . call_count , 1 );
ASSERT_EQ ( timer_start_fake . arg0_val , 10 );
ASSERT_EQ ( timer_start_fake . arg1_val , 100 );
ASSERT_EQ ( timer_start_fake . arg2_val , cb_timeout ); /* callback provided by unit tester */
ASSERT_EQ ( timer_start_fake . arg3_val , 55 );
/* and ofcourse our custom fake correctly calls the registered callback */
ASSERT_EQ ( cb_timeout_called , 1 );
} FFF функции, такие как FAKE_VALUE_FUNC будут выполнять как объявление, так и определение поддельной функции, так и соответствующие структуры данных. Это не может быть помещено в заголовок, поскольку это приведет к множеству определений поддельных функций.
Решение состоит в том, чтобы отделить объявление и определение подделок, поместить объявление в общедоступный файл заголовка, а также определение в частный исходный файл.
Вот пример того, как это можно сделать:
/* Public header file */
#include "fff.h"
DECLARE_FAKE_VALUE_FUNC ( int , value_function , int , int );
DECLARE_FAKE_VOID_FUNC ( void_function , int , int );
DECLARE_FAKE_VALUE_FUNC_VARARG ( int , value_function_vargs , const char * , int , ...);
DECLARE_FAKE_VOID_FUNC_VARARG ( void_function_vargs , const char * , int , ...);
/* Private source file file */
#include "public_header.h"
DEFINE_FAKE_VALUE_FUNC ( int , value_function , int , int );
DEFINE_FAKE_VOID_FUNC ( void_function , int , int );
DEFINE_FAKE_VALUE_FUNC_VARARG ( int , value_function_vargs , const char * , int , ...);
DEFINE_FAKE_VOID_FUNC_VARARG ( void_function_vargs , const char * , int , ...); Вы можете указать атрибуты функции GCC для ваших подделок, используя директиву FFF_GCC_FUNCTION_ATTRIBUTES .
Одним из усовершенствованных атрибутов является слабый атрибут, который отмечает функцию, так что он может быть переопределен вариантом без волны во время ссылки. Использование слабых функций в сочетании с FFF может помочь упростить ваш подход к тестированию.
Например:
Вы можете отметить все подделки со слабым атрибутом, как SO:
#define FFF_GCC_FUNCTION_ATTRIBUTES __attribute__((weak))
#include "fff.h"
См. Пример проекта, который демонстрирует приведенный выше подход: ./examples/weak_ling .
Посмотрите под каталог примеров для полной длины примеров как в C, так и в C ++. Существует также тестовый набор для рамки под тестовым каталогом.
Так в чем дело?
| Макро | Описание | Пример |
|---|---|---|
| Fake_void_func (fn [, arg_types*]); | Определите фальшивую функцию с именем FN Returing void с помощью n аргументов | Fake_void_func (show_output_message, const char*); |
| Fake_value_func (return_type, fn [, arg_types*]); | Определите фальшивую функцию, возвращая значение с типом return_type, принимая n аргументы | Fake_value_func (int, display_get_line_insert_index); |
| Fake_void_func_vararg (fn [, arg_types*], ...); | Определите фальшивую переменную функцию, возвращающую пустоту с типом return_type, принимая n аргументы и n variadic аргументы | Fake_void_func_vararg (fn, const char*, ...) |
| Fake_value_func_vararg (return_type, fn [, arg_types*], ...); | Определите фальшивую переменную функцию, возвращающую значение с типом return_type, принимая n аргументы и n variadic аргументы | Fake_value_func_vararg (int, fprintf, file*, const char*, ...) |
| Reset_fake (fn); | Сбросить состояние поддельной функции, называемое FN | Reset_fake (display_init); |