2009-02-27 31 views
33

Applicando unit test a qualche codice C, si verifica 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?Come testare una funzione statica

+0

Possibile duplicato di [Come posso testare una classe che ha metodi privati, campi o classi interne?] (Https://stackoverflow.com/questions/34571/how-do-i-test-a-class-that -has-private-methods-fields-or-inner-classes) – Raedwald

risposta

42

Ho un cablaggio di prova. In casi terribili - come cercare di testare una funzione statica, io uso:

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

Cioè, includo tutto il file contenente la funzione in prova nel test harness. È l'ultima risorsa, ma funziona.

+0

Penso che questo dovrebbe funzionare senza alcun impatto. –

+0

Non ho capito questo. Qualcuno può spiegare per favore. Grazie! –

+1

@BhupeshPant: cosa non capisci? Se una funzione è 'statica', non è accessibile al di fuori dell'unità di traduzione (grosso modo, file sorgente) dove è definita. La soluzione nella risposta assicura che la funzione da testare sia inclusa nel codice che la verifica copiando il codice sorgente da testare nel file che lo testa tramite la direttiva '#include" code_under_test.c "'. Il compilatore vede il codice da testare e il codice che fa il test tutto come un'unica unità di traduzione, quindi il codice di test può chiamare la funzione statica, qualcosa che altrimenti sarebbe impossibile. –

2

È possibile 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 
+0

Questo modifica la sorgente, vero? –

+0

Sì, modifica l'origine, ma non rimuove lo "statico" da foo. Altrimenti, la risposta è praticamente che non è possibile chiamare una statica dall'esterno. Mi sono preso una libertà. –

5

No: non è possibile testare direttamente una funzione statica senza modificare la sorgente almeno un po '(ovvero la definizione di statico in C - che non può essere chiamata da una funzione in un file diverso).

È possibile creare una funzione separata all'interno del file di test che richiama solo la funzione statica?

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

solito non testa i funzioni statiche direttamente, ma piuttosto in modo che la logica eseguono sia adeguatamente testato da diversi test della funzione chiamante.

+2

Questo non è un vero test di unità se non si testano direttamente le funzioni. –

11

Puoi fornire ulteriori informazioni sul motivo per cui non è possibile chiamare la funzione?

Non è disponibile perché è privato in 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. Ad 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() ... 
6

per unit test, in realtà hanno il codice di prova all'interno della sorgente se 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 consente di chiamare i test di unità da un singolo programma di super-test che verifica tutte le unità di compilazione.

Quando spediciamo il codice, abbiamo condizionalmente compiliamo i test di unità, ma questo non è effettivamente necessario (se si vuole essere certi che stai spedizione esattamente lo stesso codice hai provato).

Abbiamo sempre trovato inestimabile avere i test unitari nella stessa posizione del codice che si sta testando poiché rende più ovvio che è necessario aggiornare i test se e quando il codice cambia.

+1

Lo faccio quando il test unitario è abbastanza piccolo da adattarsi. Quando i test unitari diventano più grandi della sorgente sottoposta a test, ricorro a programmi di test separati, che possono essere collegati all'oggetto (quando si usano solo le dipendenze esterne) o usare il trucco nella mia risposta quando si gioca con le funzioni statiche. –

2

Le funzioni statiche sono essenzialmente funzioni di supporto per le funzioni pubbliche (ad es.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 deve essere utilizzato per testare l'effetto della statica.

Ciò significa che è necessario disporre di tronchetti appropriati per "catturare" questi effetti collaterali. (ad esempio, se una funzione chiama il file IO, è necessario fornire stub per sovrascrivere le 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.

In ogni caso, questo è l'approccio che ho usato finora e funziona per me. Buona fortuna

2

Se si è in ambiente Unix è possibile 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.

0

Ci sono 2 modi per farlo.

  1. Includere il file di origine c nel file di origine unit test, quindi il metodo statico ora è nel campo di applicazione della sorgente unit testing e richiamabile.

  2. fare un trucco:

#define static

nella testa del file di unità di fonte di prova.

Converte la parola chiave static in "niente" o, si può dire, rimuove static dal codice sorgente c.

In qualche strumento di test di unità, possiamo usare config opzione "pre-processor" per fare questo trucco, appena messo in config:

static=

Lo strumento willl convertire tutto static parola chiave per "niente "

Ma devo dire che, utilizzando questi metodi, il metodo static (o variabile) perde il significato specifico di esso.

+1

L'esposizione improvvisa di molte funzioni e variabili dichiarate statiche nell'ambito del file potrebbe portare a conflitti che non dovrebbero verificarsi. Più seriamente, improvvisamente la conversione di variabili dichiarate statiche nell'ambito della funzione (all'interno di una funzione) in non-static in genere distrugge completamente le funzioni. Quindi, il trucco #define static/* as nothing */'non è affidabile. –

+0

Sì, hai ragione. È solo un trucco nel caso in cui abbiamo preso in considerazione ciò che dobbiamo testare, solo il contenuto della funzione statica o tutte le cose si riferiscono ad esso. – Nik

+1

È interessante notare che '#define static/* as nothing * /' è proprio ciò che raccomanda cmocka (il framework di test dell'unità evoluto da cmockery di Google) (che è ciò che mi ha fatto ripensare all'uso di esso) – hmijail

1
#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; 
} 

Si potrebbe chiamare la funzione dal test di unità, come func() verrebbe esportato, tuttavia la funzionalità di base del codice sarebbe modificata.

--kurt

1

Se stai usando ceedling e cercando di utilizzare il metodo # include "code_under_test.c", il test build fallirà perché cercherà automaticamente di costruire "code_under_test.c" una volta quando #incluso e anche perché è l'obiettivo 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. Avvolgere l'intero file code_under_test.c con questo controllo:

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

Aggiungi questo al vostro test harness:

#define BUILD 
#include "code_under_test.c" 

Aggiungere il BUILD definire al Makefile o progetto di configurazione del file:

# 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 costruire il file una seconda volta (assicurarsi che il file project.yml NON definisca BUILD).

1

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

Dite la vostra funzione da testare viene

static int foo(int); 

Fate un altro file di intestazione chiamato testing_headers.h che avrà il contenuto -

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

Ora, mentre compilare il file C per la verifica si può force include questa intestazione dalle opzioni del compilatore.

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

Ciò richiederà assolutamente 0 modifiche al file c e sarà possibile scrivere un wrapper non statico per la funzione.

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

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

0

solo per aggiungere alla risposta accettata da Jonathan Leffler, ed elaborare di altra menzione di una funzione wrapper:

  1. Creare un file sorgente di prova, 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 foo statico foo.c trovi prototipo: int foo (void);

  1. accumulo test_wrapper_foo.c attraverso il vostro makefile invece di foo.c (notare che questo non si romperà le dipendenze sulle funzioni in foo.c da altre funzioni esterne)

  2. Nello script di test dell'unità, chiamare test_wrapper_foo() anziché foo().

Questo approccio lascia intatta la fonte originale, mentre consente di accedere alla funzione dal framework di test.

Problemi correlati