2013-06-06 14 views
14

Sono uno sviluppatore C++ e quando si tratta di test, è facile testare una classe iniettando dipendenze, sovrascrivendo le funzioni membro e così via, in modo da poter verificare facilmente i casi limite. Tuttavia, in C, non è possibile utilizzare quelle funzioni meravigliose. Sto trovando difficile aggiungere test di unità al codice a causa di alcuni dei metodi "standard" in cui è scritto il codice C. Quali sono i modi migliori per affrontare il seguente:Test dell'unità di scrittura per codice C

Passando intorno ad un grande 'contesto' struct puntatore:

void some_func(global_context_t *ctx, ....) 
{ 
    /* lots of code, depending on the state of context */ 
} 

modo semplice al fallimento del test sulle funzioni dipendenti:

void some_func(....) 
{ 
    if (!get_network_state() && !some_other_func()) { 
    do_something_func(); 
    .... 
    } 
    ... 
} 

Funzioni con molti parametri:

void some_func(global_context_t *, int i, int j, other_struct_t *t, out_param_t **out, ...) 
{ 
    /* hundreds and hundreds of lines of code */ 
} 

statiche o nascosti funzioni:

static void foo(...) 
{ 
    /* some code */ 
} 

void some_public_func(... } 
{ 
    /* call static functions */ 
    foo(...); 
} 
+0

[Possibile duplicato] (http://stackoverflow.com/questions/65820/unit-testing-c-code), se non solo correlato. – ajp15243

+0

@ ajp15243: Sono meno interessato a quale framework o strumento utilizzare quando si esegue il test dell'unità. Piuttosto, mi interessano i modi per scrivere test per il codice C che è altamente dipendente o usa idiomi C comuni, come una struttura 'global_context_t' che viene passata in giro. – MarkP

+0

Bene, modificherei la parte "duplicata" se potessi a questo punto. Pensavo potessi trovare almeno i quadri utili, ma vabbè: /. – ajp15243

risposta

8

In generale, sono d'accordo con la risposta di Wes - sarà molto più difficile aggiungere test al codice che non è stato scritto con i test in mente. Non c'è nulla di intrinseco in C che rende impossibile testare, ma poiché C non ti obbliga a scrivere in uno stile particolare, è anche molto facile scrivere codice C difficile da testare.

A mio parere, la scrittura di codice con test in mente incoraggerà funzioni più brevi, con pochi argomenti, che aiuta ad alleviare parte del dolore nei vostri esempi.

In primo luogo, è necessario selezionare un framework di test dell'unità. Ci sono molti esempi in this question (anche se purtroppo molte delle risposte sono framework C++ - sconsiglio di usare C++ per testare C).

Io personalmente uso TestDept, perché è semplice da usare, leggero e consente lo stub. Tuttavia, non penso che sia ancora molto usato. Se stai cercando un framework più popolare, molte persone raccomandano lo Check - il che è ottimo se usi automake.

Ecco alcune risposte specifiche per i vostri casi d'uso:

passando intorno una grande 'contesto' struct puntatore

Per questo caso, si può costruire un'istanza della struct con le condizioni pre impostate manualmente, quindi controllare lo stato della struttura dopo che la funzione è stata eseguita. Con funzioni brevi, ogni test sarà abbastanza semplice.

modo semplice al fallimento del test sulle funzioni dipendenti

Credo che questo sia uno dei più grandi ostacoli con unità di test C. che ho avuto successo con TestDept, che permette di tempo di esecuzione stub delle funzioni dipendenti. Questo è ottimo per spezzare il codice strettamente accoppiato. Ecco un esempio dalla loro documentazione:

void test_stringify_cannot_malloc_returns_sane_result() { 
    replace_function(&malloc, &always_failing_malloc); 
    char *h = stringify('h'); 
    assert_string_equals("cannot_stringify", h); 
} 

A seconda dell'ambiente di destinazione, questo potrebbe non funzionare per voi. Vedi their documentation per maggiori dettagli.

funzioni con un sacco di parametri

questo probabilmente non è la risposta che stai cercando, ma vorrei solo spezzare questi fino in funzioni più piccole con meno parametri. Molto più facile da testare.

funzioni statiche o nascoste

non è super pulito, ma ho provato funzioni statiche includendo il file sorgente direttamente, consentendo chiamate di funzioni statiche. Combinato con TestDept per eliminare tutto ciò che non è sotto test, funziona abbastanza bene.

#include "implementation.c" 

/* Now I can call foo(), defined static in implementation.c */ 

un sacco di codice C è il codice legacy con poche prove - e in quei casi, è generalmente più facile per aggiungere i test di integrazione che mettono alla prova le grandi parti del codice prima, invece di unit test grana fine . Ciò consente di avviare il refactoring del codice al di sotto del test di integrazione a uno stato verificabile dall'unità, sebbene possa valere o meno l'investimento, a seconda della situazione.Ovviamente, vorrai essere in grado di aggiungere test unitari a qualsiasi nuovo codice scritto durante questo periodo, quindi avere una solida infrastruttura pronta per l'uso è una buona idea.

Se si con codice legacy, this book (Lavorare efficacemente con codice legacy di Michael Feathers) è un'ottima lettura.

+1

Buona risposta anche; +1 da me :-) –

+1

Ottima risposta! – MarkP

8

E 'stata una buona domanda progettato per attirare la gente a credere che il C++ è meglio di C, perché è più verificabili. Tuttavia, difficilmente è così semplice.

Avendo scritto tanto codice C++ e C testabile entrambi, e una quantità altrettanto impressionante di codice C++ e C non testabile, posso dire in tutta confidenza che è possibile eseguire il wrapping di codice non testabile in entrambe le lingue. In effetti, la maggior parte dei problemi che presenti sopra sono altrettanto problematici in C++. Per esempio, molte persone scrivono funzioni incapsulate non-object in C++ e le usano all'interno di classi (vedi l'uso esteso delle funzioni statiche di C++ all'interno delle classi, ad esempio, come MyAscii :: fromUtf8(), funzioni di tipo).

E sono abbastanza sicuro che hai visto un gazillion funzioni di classe C++ con troppi parametri. E se pensi che solo perché una funzione ha un solo parametro è meglio, considera il caso che internamente spesso maschera i parametri passati usando un gruppo di variabili membro. Per non parlare delle funzioni "statiche o nascoste" (suggerimento, ricorda che la parola chiave "privato:") è altrettanto grande di un problema.

Quindi, la vera risposta alla tua domanda non è "C è peggio per esattamente le ragioni che dici" ma piuttosto "devi architettarlo correttamente in C, proprio come faresti in C++". Ad esempio, se si dispone di funzioni dipendenti, inserirle in un file diverso e restituire il numero di possibili risposte che potrebbero fornire implementando una versione fasulla di tale funzione durante il test della super-funzione. E questo è il cambiamento a malapena. Non creare funzioni statiche o nascoste se vuoi testarle.

Il vero problema è che sembra affermare nella tua domanda che stai scrivendo dei test per la libreria di qualcun altro che non hai scritto e architetti per la testabilità corretta. Tuttavia, ci sono un sacco di librerie C++ che mostrano gli stessi identici sintomi e se ne venisse consegnato uno per testare, saresti altrettanto altrettanto infastidito.

La soluzione a tutti i problemi di questo tipo è sempre la stessa: scrivere il codice correttamente e non utilizzare il codice scritto in modo improprio di qualcun altro.

+0

Home run. +1 per dirlo come è. –

+0

Grazie @RandyHoward, apprezzo molto il complimento. –

+0

Ottimo consiglio, ho anche svalutato questo. Penso che C sia meno indulgente nell'aggiungere test dopo il fatto rispetto ad altri linguaggi, il che porta a una cattiva reputazione. Ma puoi * assolutamente * scrivere pulito, verificabile C. –

1

Quando si esegue il test dell'unità C normalmente si include il file .c nel test in modo da poter prima testare le funzioni statiche prima di testare quelle pubbliche.

Se si dispone di funzioni complesse e si desidera testare il codice chiamandole, è possibile lavorare con oggetti fittizi. Dai uno sguardo al framework di test delle unità cmocka che offre supporto per gli oggetti mock.

Problemi correlati