Come testare una funzione statica

Applicando il test unitario ad un certo codice C, ci imbattiamo in un problema nel fatto che alcune funzioni statiche non possono essere richiamate nel file di test, senza modificare il codice sorgente. C’è un modo semplice o ragionevole per superare questo problema?

Ho un cablaggio di prova. In casi gravi – come provare a testare una funzione statica, io uso:

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

Cioè, includo l’intero file contenente la funzione sotto test nel cablaggio di test. È l’ultima risorsa, ma funziona.

Puoi dare maggiori informazioni sul motivo per cui non puoi chiamare la funzione?

Non è disponibile perché è privato di un file .c? In questo caso, la soluzione migliore è utilizzare la compilazione condizionale che consente l’accesso alla funzione per consentire ad altre unità di compilazione di accedervi. Per esempio

SomeHeaderSomewher.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() ... 

Per i test unitari, abbiamo effettivamente il codice di test all’interno del file sorgente stesso e lo compiliamo condizionatamente durante il test. Ciò consente all’unità di testare l’accesso completo a tutte le funzioni e variabili a livello di file (statiche o meno).

I test di unità non sono statici, questo ci permette di chiamare i test di unità da un singolo programma di super-test che verifica tutte le unità di compilazione.

Quando spediamo il codice, compiliamo condizionatamente i test unitari ma questo non è effettivamente necessario (se vuoi essere certo di spedire esattamente lo stesso codice che hai testato).

Abbiamo sempre trovato inestimabile avere i test unitari nello stesso posto del codice che stai testando, dal momento che rende più ovvio che devi aggiornare i test se e quando il codice cambia.

No, non è ansible testare direttamente una funzione statica senza modificare almeno una parte della sorgente (ovvero la definizione di staticità in C – che non può essere chiamata da una funzione in un file diverso).

È ansible creare una funzione separata all’interno del file di test che chiama solo la funzione statica?

Per esempio:

 //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); } 

Solitamente non testiamo direttamente le nostre funzioni statiche, ma piuttosto assicuriamo che la logica che eseguono sia adeguatamente testata da diversi test della funzione chiamante.

È ansible aggiungere una funzione non statica per chiamare la funzione statica, quindi chiamare la funzione non statica.

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

le funzioni statiche sono essenzialmente funzioni di supporto per le funzioni pubbliche (cioè esposte). Quindi, IMO, i test di unità dovrebbero chiamare l’interfaccia pubblica con gli input che esercitano tutti i percorsi nella funzione statica.

L’output (valori di ritorno / effetti collaterali) della funzione pubblica dovrebbe essere usato per testare l’effetto della statica.

Ciò significa che è necessario disporre di stub appropriati per “catturare” questi effetti collaterali. (ad es. se una funzione chiama il file IO, è necessario fornire stub per sovrascrivere queste funzioni di I / O lib di file). Il modo migliore per farlo rendendo ogni suite di test un progetto / eseguibile separato ed evitare il collegamento a qualsiasi funzione di lib esterna. Puoi prendere in giro anche le funzioni C, ma non ne vale la pena.

Ad ogni modo, questo è l’approccio che ho usato finora e funziona per me. In bocca al lupo

Se si è in ambiente Unix, è ansible includere nel file di test un’intestazione aggiuntiva yourheader_static.h con dichiarazioni delle funzioni statiche e tradurre il file obj code_under_test.o tramite objdump --globalize-symbols=syms_name_file per globalizzare i simboli locali. Saranno visibili come se fossero funzioni non statiche.

 #define static 

Questa è una pessima idea. Se hai una variabile dichiarata locale in una funzione, cambia il comportamento della funzione. Esempio:

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

È ansible chiamare la funzione dal test di unità, in quanto func () verrebbe esportata, tuttavia la funzionalità di base del codice verrebbe modificata.

–kurt

Se stai usando Ceedling e stai provando ad usare il metodo # include “code_under_test.c”, la generazione del test fallirà perché tenterà automaticamente di creare “code_under_test.c” una volta quando #include e anche perché è il bersaglio del test .

Sono stato in grado di aggirare il problema con una leggera modifica al codice code_under_test.c e un paio di altre modifiche. Avvolgi l’intero file code_under_test.c con questo controllo:

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

Aggiungi questo al tuo cablaggio di prova:

 #define BUILD #include "code_under_test.c" 

Aggiungi la definizione BUILD al tuo Makefile o al file di configurazione del progetto:

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

Il tuo file verrà ora creato dal tuo ambiente e quando incluso dal tuo cablaggio di prova. Ceedling ora non sarà in grado di build il file una seconda volta (assicurarsi che il file project.yml NON definisca BUILD).

Tutte le risposte suggerite sopra (tranne alcune) suggeriscono una compilazione condizionale che richiede una modifica all’origine. Come tale che non dovrebbe essere un problema, aggiungerebbe solo confusione (solo per i test). Piuttosto puoi fare qualcosa di simile.

Dì che la tua funzione da testare è

 static int foo(int); 

Fai un altro file header chiamato testing_headers.h che avrà il contenuto –

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

Ora durante la compilazione del tuo file c per i test puoi forzare includere questa intestazione dalle opzioni del compilatore.

Per clang e gcc la flag è --include . Per il compilatore Microsoft C è /FI .

Ciò richiederà assolutamente 0 modifiche al tuo file c e sarai in grado di scrivere un wrapper non statico per la tua funzione.

Se non si desidera un wrapper non statico, è anche ansible creare un puntatore a funzione globale non statico inizializzato su foo.

È quindi ansible chiamare la funzione utilizzando questo puntatore a funzioni globali.

Ci sono 2 modi per farlo.

  1. Includere il file sorgente c nel file sorgente di test dell’unità, quindi il metodo statico ora si trova nell’ambito del file sorgente di test dell’unità e può essere richiamato.

  2. Fai un trucco:

#define static

nella testa del file sorgente di test dell’unità.

Converte la parola chiave static in “nothing” o posso dire che rimuove la static dal tuo c source code.

In alcuni strumenti di test delle unità, possiamo usare l’opzione di configurazione “pre-processore” per fare questo trucco, basta mettere nella configurazione:

static=

Lo strumento convertirà tutte static parole chiave static in “niente”

Ma devo dire che, usando questi metodi, il metodo static (o variabile) perde il suo significato specifico.

Giusto per aggiungere la risposta accettata di Jonathan Leffler, e approfondire la menzione di un’altra funzione di wrapper:

  1. Creare un file sorgente di test, come in test_wrapper_foo.c, dove foo.c è l’originale.
  2. In test_wrapper_foo.c:
 #include "foo.c" // Prototype int test_wrapper_foo(); // wrapper function int test_wrapper_foo() { // static function to test return foo(); } 

Supponendo che la funzione statica foo in foo.c abbia un prototipo: int foo (void);

  1. crea test_wrapper_foo.c attraverso il tuo makefile invece di foo.c (nota che questo non infrangerà alcuna dipendenza dalle funzioni di foo.c da altre funzioni esterne)

  2. Nello script di test dell’unità, chiama test_wrapper_foo () invece di foo ().

Questo approccio lascia intatta la fonte originale, mentre ti dà accesso alla funzione dal tuo framework di test.