FFF ist ein Micro-Framework, um gefälschte C-Funktionen für Tests zu erstellen. Denn das Leben ist zu kurz, um Zeit mit Handschreiber gefälschten Funktionen zum Testen zu verbringen.
Um alle Tests und Beispiel -Apps auszuführen, rufen Sie einfach $ buildandtest an. Dieses Skript wird mit Folgendem in CMake aufgerufen:
cmake -B build -DFFF_GENERATE=ON -DFFF_UNIT_TESTING=ON
cmake --build build
ctest --test-dir build --output-on-failureAngenommen, Sie testen eine eingebettete Benutzeroberfläche und haben eine Funktion, für die Sie eine Fälschung erstellen möchten:
// UI.c
...
void DISPLAY_init ();
...So definieren Sie eine gefälschte Funktion hierfür in Ihrer Testsuite:
// test.c(pp)
#include "fff.h"
DEFINE_FFF_GLOBALS ;
FAKE_VOID_FUNC ( DISPLAY_init );Und der Unit -Test könnte ungefähr so aussehen:
TEST_F ( GreeterTests , init_initialises_display )
{
UI_init ();
ASSERT_EQ ( DISPLAY_init_fake . call_count , 1 );
} Also, was ist hier passiert? Das erste, was zu beachten ist, ist, dass das Framework nur Header ist. Alles, was Sie tun müssen, um es zu verwenden, ist Download fff.h und in Ihre Testsuite einbezogen.
Die Magie ist in der FAKE_VOID_FUNC . Dadurch wird ein Makro erweitert, das eine Funktion definiert, die eine void zurückgibt, die keine Argumente aufweist. Es definiert auch eine Struktur "function_name"_fake , die alle Informationen über die Fälschung enthält. Zum Beispiel wird DISPLAY_init_fake.call_count jedes Mal inkrementiert, wenn die gefälschte Funktion aufgerufen wird.
Unter der Motorhaube erzeugt es eine Struktur, die so aussieht:
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 ;Ok, genug mit den Spielzeugbeispielen. Was ist mit Faking -Funktionen mit Argumenten?
// UI.c
...
void DISPLAY_output ( char * message );
...So definieren Sie eine gefälschte Funktion hierfür in Ihrer Testsuite:
FAKE_VOID_FUNC ( DISPLAY_output , char * );Und der Unit -Test könnte ungefähr so aussehen:
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 );
} Hier gibt es keine Magie mehr, die FAKE_VOID_FUNC funktioniert wie im vorherigen Beispiel. Die Anzahl der Argumente, die die Funktion ausübt, wird berechnet, und die Makroargumente, die dem Funktionsnamen folgen, definiert den Argumentyp (ein Zeichenzeiger in diesem Beispiel).
Für jedes Argument in der Form "function_name"fake.argN_val wird eine Variable erstellt
Wenn Sie eine gefälschte Funktion definieren möchten, die einen Wert zurückgibt, sollten Sie das Makro FAKE_VALUE_FUNC verwenden. Zum Beispiel:
// UI.c
...
unsigned int DISPLAY_get_line_capacity ();
unsigned int DISPLAY_get_line_insert_index ();
...So definieren Sie gefälschte Funktionen für diese in Ihrer Testsuite:
FAKE_VALUE_FUNC ( unsigned int , DISPLAY_get_line_capacity );
FAKE_VALUE_FUNC ( unsigned int , DISPLAY_get_line_insert_index );Und der Unit -Test könnte ungefähr so aussehen:
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 );
}Natürlich können Sie diese Makros mischen und anpassen, um eine Wertfunktion mit Argumenten zu definieren, beispielsweise zu Fälschung:
double pow ( double base , double exponent );Sie würden eine solche Syntax verwenden:
FAKE_VALUE_FUNC ( double , pow , double , double );Gute Tests sind isolierte Tests. Daher ist es wichtig, die Fälschungen für jeden Unit -Test zurückzusetzen. Alle Fälschungen haben eine Reset -Funktion, um ihre Argumente zurückzusetzen und Zählungen aufzurufen. Es ist eine gute Praxis, die Reset -Funktion für alle Fälschungen in der Setup -Funktion Ihrer Testsuite aufzurufen.
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 );
}Vielleicht möchten Sie ein Makro definieren, um dies zu tun:
/* 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 ();
}Sagen Sie, Sie möchten testen, dass eine Funktion die Funktiona aufruft, dann FunktionB und dann noch einmal die Funktionen, wie würden Sie das tun? Nun, FFF behält eine Anrufverlauf bei, damit es leicht ist, diese Erwartungen zu gründen.
So funktioniert es:
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 );
} Sie werden zurückgesetzt, indem sie FFF_RESET_HISTORY();
Das Framework wird standardmäßig die Argumente für die letzten zehn Anrufe für eine gefälschte Funktion speichern.
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 ]);
}Es gibt zwei Möglichkeiten, um herauszufinden, ob Anrufe fallen gelassen wurden. Das erste besteht darin, den abgebrochenen Geschichtenzähler zu überprüfen:
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 );
}Das andere ist zu prüfen, ob die Anrufzahl größer ist als die Verlaufsgröße:
ASSERT ( voidfunc2_fake . arg_history_len < voidfunc2_fake . call_count ); Die Argumentierhistorien für eine gefälschte Funktion werden zurückgesetzt, wenn die Funktion RESET_FAKE aufgerufen wird
Wenn Sie steuern möchten, wie viele Aufrufe zum Argument -Historie erfasst werden können, können Sie den Standard überschreiben, indem Sie ihn vor dem Definieren des fff.h wie folgt einschließen:
// 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" Oft möchten wir beim Test das Verhalten der Abfolge von Funktionsaufrufereignissen testen. Eine Möglichkeit, dies mit FFF zu tun, besteht darin, eine Abfolge von Rückgabetwerten für die gefälschte Funktion anzugeben. Es ist wahrscheinlich einfacher zu beschreiben mit einem Beispiel:
// 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 ());
} Durch Angeben einer Rückwert -Wertsequenz mit dem Makro SET_RETURN_SEQ gibt die Fälschung die im Parameter -Array angegebenen Werte in Sequenz zurück. Wenn das Ende der Sequenz erreicht ist, wird die Fälschung weiterhin den letzten Wert in der Sequenz auf unbestimmte Zeit zurückgeben.
Sie können Ihre eigene Funktion angeben, um den Rückgabewert für die Fälschung anzugeben. Dies erfolgt durch Einstellen des custom_fake -Mitglieds der Fälschung. Hier ist ein Beispiel:
#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 );
} Angenommen, Sie haben eine Funktion mit einem Out -Parameter, und Sie möchten, dass es bei den ersten drei Aufrufen ein anderes Verhalten hat, zum Beispiel: Setzen Sie den Wert 'x' auf den Out -Parameter auf dem ersten Anruf, den Wert 'y' auf den Out -Parameter auf dem zweiten Anruf und den Wert 'Z' zum Out -Parameter auf dem dritten Anruf. Sie können eine Sequenz benutzerdefinierter Funktionen für eine nicht variadische Funktion mit dem Makro SET_CUSTOM_FAKE_SEQ angeben. Hier ist ein Beispiel:
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 );
} Die Fälschung ruft Ihre benutzerdefinierten Funktionen in der von SET_CUSTOM_FAKE_SEQ MAKRO angegebenen Bestellung auf. Wenn die letzte benutzerdefinierte Fälschung erreicht ist, ruft die Fälschung die letzte benutzerdefinierte Fälschung in der Sequenz weiter. Dieses Makro funktioniert dem Makro SET_RETURN_SEQ .
Angenommen, Sie haben zwei Funktionen F1 und F2. F2 muss aufgerufen werden, um einige von F1 zugewiesene Ressource freizugeben, jedoch nur in den Fällen, in denen F1 Null zurückgibt. F1 könnte pthread_mutex_trylock sein und f2 könnte pthread_mutex_unlock sein. FFF speichert die Geschichte der zurückgegebenen Werte, sodass dies leicht überprüft werden kann, selbst wenn Sie eine Folge von benutzerdefinierten Fälschungen verwenden. Hier ist ein einfaches Beispiel:
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]);
}
Sie greifen auf die zurückgegebenen Werte im Feld return_val_history zu.
Sie können variadische Funktionen mit den Makros FAKE_VALUE_FUNC_VARARG und FAKE_VOID_FUNC_VARARG fälschen. Zum Beispiel:
FAKE_VALUE_FUNC_VARARG(int, fprintf, FILE *, const char*, ...);
Um auf die variadischen Parameter aus einer benutzerdefinierten gefälschten Funktion zuzugreifen, deklarieren Sie einen va_list -Parameter. Zum Beispiel könnte eine benutzerdefinierte Fälschung für fprintf() den echten fprintf() wie folgt aufrufen:
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);
}
Genau wie bei den Rückgabewertdelegierten können Sie auch Sequenzen für variadische Funktionen mithilfe SET_CUSTOM_FAKE_SEQ angeben. Beispiele finden Sie in den Testdateien.
FFF verfügt über eine begrenzte Fähigkeit, die Spezifikation der visuellen C/C ++ -Nach "von Microsoft zu aktivieren. Diese Unterstützung muss jedoch bei der Generierung von FFF -Header -Datei fff.h von FFF aktiviert werden.
ruby fakegen.rb --with-calling-conventions > fff.h Durch die Aktivierung dieser Unterstützung erfordert das gesamte gefälschte Funktionsgerüst von FFF die Spezifikation einer Anrufkonvention, z __cdecl
Hier sind einige grundlegende Beispiele: Beachten Sie, dass die Platzierung der angegebenen Aufrufkonvention unterschiedlich ist, je nachdem, ob die Fälschung eine Hohlraum- oder Wertfunktion ist.
FAKE_VOID_FUNC ( __cdecl , voidfunc1 , int );
FAKE_VALUE_FUNC ( long , __cdecl , longfunc0 );Der grundlegende Mechanismus, den FFF in diesem Fall bereitstellt, ist das Feld Custom_Fake, das im oben genannten Beispiel für den benutzerdefinierten Rückgabewert beschrieben wird.
Sie müssen eine benutzerdefinierte Funktion (z. B. GetTime_Custom_Fake) erstellen, um die Ausgabe optional mithilfe einer Helfervariablen (z. B. GetTime_Custom_Now) zu erstellen, um diese Ausgabe abzurufen. Dann etwas Kreativität, um alles zusammen zu binden. Der wichtigste Teil (IMHO) besteht darin, Ihren Testfall lesbar und wartbar zu halten.
Falls Ihr Projekt einen C -Compiler verwendet, der verschachtelte Funktionen (z. B. GCC) unterstützt, oder wenn Sie C ++ Lambdas verwenden, können Sie dies sogar in einer einzelnen Testfunktion in einer Einheit kombinieren, damit Sie alle Details des Tests problemlos überwachen können.
#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 );
}Die Verwendung von FFF zu Stubfunktionen, die einen Funktionszeigerparameter haben, kann Probleme verursachen, wenn Sie versuchen, sie zu stützen. Hier ist ein Beispiel, wie man mit dieser Situation umgeht.
Wenn Sie eine Funktion mit einem Funktionszeigerparameter steigen müssen, z. B. so etwas wie:
/* timer.h */
typedef int timer_handle ;
extern int timer_start ( timer_handle handle , long delay , void ( * cb_function ) ( int arg ), int arg ); Wenn Sie dann eine Fälschung wie unten erstellen, wird beim Kompilieren schrecklich fehlgeschlagen, da das FFF -Makro intern in eine illegale variable int (*)(int) arg2_val ausgeweitet wird.
/* The fake, attempt one */
FAKE_VALUE_FUNC ( int ,
timer_start ,
timer_handle ,
long ,
void ( * ) ( int argument ),
int );Die Lösung für dieses Problem besteht darin, einen Überbrückungsart zu erstellen, der nur im Unit -Tester sichtbar ist. Die Fälschung wird diesen Zwischentyp verwenden. Auf diese Weise beschwert sich der Compiler nicht, da die Typen übereinstimmen.
/* 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 );Hier sind einige Ideen, wie man einen Testfall mit Rückrufen erstellt.
/* 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 -Funktionen wie FAKE_VALUE_FUNC führen sowohl die Definition als auch die Definition der Fake -Funktion und die entsprechenden Datenstrukturen durch. Dies kann nicht in einen Header gestellt werden, da dies zu mehreren Definitionen der gefälschten Funktionen führt.
Die Lösung besteht darin, die Erklärung und Definition der Fälschungen zu trennen und die Erklärung in eine öffentliche Header -Datei und die Definition in eine private Quelldatei zu legen.
Hier ist ein Beispiel dafür, wie es getan werden könnte:
/* 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 , ...); Sie können GCC -Funktionsattribute für Ihre Fälschungen unter Verwendung der Anweisung FFF_GCC_FUNCTION_ATTRIBUTES angeben.
Ein uswules Attribut ist das schwache Attribut, das eine Funktion markiert, sodass es zur Verknüpfungszeit durch eine nicht-Weak-Variante überschrieben werden kann. Die Verwendung schwacher Funktionen in Kombination mit FFF kann dazu beitragen, Ihren Testansatz zu vereinfachen.
Zum Beispiel:
Sie können alle Fälschungen mit dem schwachen Attribut wie SO markieren:
#define FFF_GCC_FUNCTION_ATTRIBUTES __attribute__((weak))
#include "fff.h"
Siehe das Beispielprojekt, das den obigen Ansatz demonstriert: ./Examples/weak_linking .
Schauen Sie sich unter dem Beispielverzeichnis in voller Länge nach Beispielen in C- und C ++. Es gibt auch eine Testsuite für das Framework im Testverzeichnis.
Also, was ist der Punkt?
| Makro | Beschreibung | Beispiel |
|---|---|---|
| Fake_void_func (fn [, arg_types*]); | Definieren Sie eine gefälschte Funktion namens FN, die mit N Argumenten Leere zurückgibt | Fake_void_func (display_output_message, const char*); |
| Fake_value_func (return_type, fn [, arg_types*]); | Definieren Sie eine gefälschte Funktion, die einen Wert zurückgibt, wobei Typ retyp_type n Argumente nimmt | Fake_value_func (int, display_get_line_insert_index); |
| Fake_void_func_vararg (fn [, arg_types*], ...); | Definieren Sie eine gefälschte variadische Funktion, die Leere mit Typ return_type nimmt Argumente und n variadische Argumente zurück | Fake_void_func_vararg (fn, const char*, ...) |
| Fake_value_func_vararg (return_type, fn [, arg_types*], ...); | Definieren Sie eine gefälschte variadische Funktion, die einen Wert mit Typ return_type nimmt und n variadische Argumente nimmt | Fake_value_func_vararg (int, fprintf, Datei*, const char*, ...) |
| Reset_fake (fn); | Setzen Sie den Zustand der gefälschten Funktion als FN zurück | Reset_fake (display_init); |