Comment tester une fonction statique

En appliquant un test unitaire à du code C, nous rencontrons un problème: certaines fonctions statiques ne peuvent pas être appelées dans le fichier de test sans modifier le code source. Existe-t-il un moyen simple ou raisonnable de résoudre ce problème?

J’ai un harnais de test. Dans des cas extrêmes – comme essayer de tester une fonction statique, j’utilise:

#include "code_under_test.c" ...test framework... 

C’est-à-dire que j’inclus l’ensemble du fichier contenant la fonction testée dans le faisceau de test. C’est un dernier recours – mais ça fonctionne.

Pouvez-vous donner plus d’informations sur la raison pour laquelle vous ne pouvez pas appeler la fonction?

N’est-il pas disponible parce qu’il est privé d’un fichier .c? Dans ce cas, le mieux est d’utiliser une compilation conditionnelle qui permet d’accéder à la fonction afin de permettre à d’autres unités de compilation d’y accéder. Par exemple

CertainsHeaderSomewher.h

 #if UNIT_TEST #define unit_static #else #define unit_static static #endif 

Foo.h

 #if UNIT_TEST void some_method #endif 

Foo.cpp

 unit_static void some_method() ... 

Pour les tests unitaires, nous avons effectivement le code de test dans le fichier source lui-même et nous le compilons conditionnellement lors du test. Cela donne aux tests unitaires un access complet à toutes les fonctions et variables de niveau fichier (statiques ou autres).

Les tests unitaires eux-mêmes ne sont pas statiques – cela nous permet d’appeler les tests unitaires à partir d’un seul programme de super-tests qui teste toutes les unités de compilation.

Lorsque nous envoyons le code, nous compilons sous condition les tests unitaires, mais ce n’est pas réellement nécessaire (si vous voulez être certain que vous envoyez exactement le même code que vous avez testé).

Nous avons toujours trouvé précieux d’avoir les tests unitaires au même endroit que le code que vous testez, car il est donc plus évident que vous devez mettre à jour les tests si et quand le code change.

Non – vous ne pouvez pas tester directement une fonction statique sans en modifier un peu la source (c’est-à-dire la définition de static en C – le fait qu’il ne puisse pas être appelé depuis une fonction située dans un autre fichier).

Vous pourriez créer une fonction distincte dans le fichier de test qui appelle simplement la fonction statique?

Par exemple:

 //Your fn to test static int foo(int bar) { int retVal; //do something return retVal; } //Wrapper fn int test_foo(int bar) { return foo(bar); } 

En général, nous ne testons pas directement nos fonctions statiques, mais nous veillons plutôt à ce que la logique qu’elles exécutent soit correctement testée par différents tests de la fonction d’appel.

Vous pouvez append une fonction non statique pour appeler la fonction statique, puis appeler la fonction non statique.

 static int foo () { return 3; } #ifdef UNIT_TEST int test_foo () { if (foo () == 3) return 0; return 1; } #endif 

Les fonctions statiques sont essentiellement des fonctions d’assistance aux fonctions publiques (c’est-à-dire exposées). Donc, OMI, vos tests unitaires doivent appeler l’interface publique avec des entrées qui exercent tous les chemins de la fonction statique.

La sortie (valeurs de retour / effets secondaires) de la fonction public doit être utilisée pour tester l’effet de la statique.

Cela signifie que vous devez disposer de stubs appropriés pour «attraper» ces effets secondaires. (Par exemple, si une fonction appelle un fichier IO, vous devez fournir des stubs pour remplacer ces fonctions de fichier IO lib). La meilleure façon de le faire consiste à transformer chaque suite de tests en un projet / exécutable séparé et à éviter la liaison à des fonctions de bibliothèque externes. Vous pouvez vous moquer des fonctions C, mais cela ne vaut pas la peine.

Quoi qu’il en soit, c’est l’approche que j’ai utilisée jusqu’à présent et elle fonctionne pour moi. Bonne chance

Si vous êtes sous environnement Unix, vous pouvez inclure dans le fichier de test un en-tête supplémentaire, yourheader_static.h avec les déclarations de vos fonctions statiques et traduire le fichier obj code_under_test.o via objdump --globalize-symbols=syms_name_file pour globaliser les symboles locaux. Ils seront visibles comme s’il s’agissait de fonctions non statiques.

 #define static 

C’est une très mauvaise idée. Si vous avez une variable déclarée locale d’une fonction, cela change le comportement de la fonction. Exemple:

 static int func(int data) { static int count = 0; count += data; return count; } 

Vous pouvez appeler la fonction à partir du test unitaire, car func () serait exporté, mais les fonctionnalités de base du code seraient modifiées.

–kurt

Si vous utilisez Ceedling et essayez d’utiliser la méthode #include “code_under_test.c”, la construction du test échouera, car elle tentera automatiquement de générer “code_under_test.c” une fois lorsque #included et aussi parce que c’est la cible du test. .

J’ai pu le contourner en modifiant légèrement le code code_under_test.c et en apportant quelques modifications. Enveloppez tout le fichier code_under_test.c avec cette vérification:

 #if defined(BUILD) ... #endif // defined(BUILD) 

Ajoutez ceci à votre harnais de test:

 #define BUILD #include "code_under_test.c" 

Ajoutez la définition BUILD à votre fichier de configuration de Makefile ou de projet:

 # Makefile example .. CFLAGS += -DBUILD .. 

Votre fichier va maintenant être construit à partir de votre environnement et une fois inclus dans votre faisceau de test. Ceedling ne sera plus en mesure de générer le fichier une seconde fois (assurez-vous que votre fichier project.yml NE définit PAS BUILD).

Toutes les réponses suggérées ci-dessus (sauf quelques-unes) suggèrent une compilation conditionnelle qui nécessite une modification de la source. En tant que tel, cela ne devrait pas être un problème, cela ne ferait qu’append du fouillis (juste pour les tests). Vous pouvez plutôt faire quelque chose comme ça.

Dites que votre fonction à tester est

 static int foo(int); 

Vous créez un autre fichier d’en-tête appelé testing_headers.h qui aura le contenu –

 static in foo(int); int foo_wrapper(int a) { return foo(a); } 

Maintenant, lors de la compilation de votre fichier c pour le test, vous pouvez forcer l’inclusion de cet en-tête à partir des options du compilateur.

Pour clang et gcc, le drapeau est --include . Pour le compilateur Microsoft C, il s’agit de /FI .

Cela nécessitera absolument 0 changement dans votre fichier c et vous pourrez écrire un wrapper non statique dans votre fonction.

Si vous ne voulez pas d’un wrapper non statique, vous pouvez également créer un pointeur de fonction global non statique initialisé à foo.

Vous pouvez ensuite appeler la fonction à l’aide de ce pointeur de fonction global.

Il y a 2 façons de faire cela.

  1. Incluez le fichier source c dans le fichier source de tests unitaires, de sorte que la méthode statique entre maintenant dans le champ du fichier source de tests unitaires et appelable.

  2. Faites un tour:

#define static

en tête du fichier source des tests unitaires.

Il convertira le mot clé static en “néant” ou, je peux le dire, il supprime la static de votre code source c.

Dans certains outils de test unitaire, nous pouvons utiliser l’option de configuration “pré-processeur” pour effectuer cette astuce, il suffit de mettre dans la configuration:

static=

L’outil convertira tous static mots clés static en “rien”

Mais je dois dire que, de ces manières, la méthode static (ou variable) perd sa signification spécifique.

Juste pour append à la réponse acceptée par Jonathan Leffler, et développer sur la mention par d’autres d’une fonction d’emballage:

  1. Créez un fichier source de test, comme dans test_wrapper_foo.c, où foo.c est l’original.
  2. Dans test_wrapper_foo.c:
 #include "foo.c" // Prototype int test_wrapper_foo(); // wrapper function int test_wrapper_foo() { // static function to test return foo(); } 

En supposant que la fonction statique foo dans foo.c ait le prototype: int foo (void);

  1. construisez test_wrapper_foo.c avec votre makefile au lieu de foo.c (notez que cela ne rompt aucune dépendance sur les fonctions de foo.c par d’autres fonctions externes)

  2. Dans votre script de test unitaire, appelez test_wrapper_foo () au lieu de foo ().

Cette approche laisse la source originale intacte, tout en vous donnant access à la fonction à partir de votre framework de test.