Come dovrebbe essere organizzato il codice di unit test C ++ per la massima efficienza di unit test?


47
  • Questa domanda non riguarda i framework di unit test.
  • Questa domanda non riguarda la scrittura di test unitari.
  • Questa domanda riguarda dove inserire il codice UT scritto e come / quando / dove compilarlo ed eseguirlo.

Nel lavorare efficacemente con il codice legacy , Michael Feathers afferma che

buoni test unitari ... corri veloce

e quello

Un test unitario che richiede 1/10 di secondo per essere eseguito è un test unitario lento.

Penso che queste definizioni abbiano un senso. Penso anche che implicano che devi tenere una serie di Test unitari e una serie di Quei test di codice che richiedono più tempo separatamente, ma immagino che sia il prezzo che paghi per chiamare qualcosa un Test unitario se funziona (molto) velocemente .

Ovviamente il problema in C ++ è quella di "correre" il tuo Test Unit ( s ), è necessario:

  1. Modifica il tuo codice (produzione o Unit Test, a seconda del "ciclo" in cui ti trovi)
  2. Compilare
  3. collegamento
  4. Inizio Unità eseguibile di prova ( s )

Modifica (dopo uno strano voto da vicino) : prima di entrare nei dettagli, proverò a riassumere il punto qui:

Come può essere organizzato in modo efficace il codice C ++ Unit Test, in modo che sia sia efficace modificare il codice (test) sia eseguirlo?


Il primo problema è quindi decidere dove inserire il codice Unit Test in modo che:

  • è "naturale" modificarlo e visualizzarlo in combinazione con il codice di produzione associato.
  • è facile / veloce avviare il ciclo di compilazione per l'unità attualmente in fase di modifica

Il secondo problema correlato è quindi cosa compilare in modo che il feedback sia istantaneo.

Opzioni estreme:

  • Ogni Unit-Test-Test-Unit vive in un file cpp separato e questo file cpp viene compilato + collegato separatamente (insieme al file di unità del codice sorgente testato) a un singolo eseguibile che quindi esegue questo test unitario.
    • (+) Ciò minimizza il tempo di avvio (compilazione + collegamento!) Per la singola unità di test.
    • (+) Il test viene eseguito molto velocemente, poiché verifica solo un'unità.
    • (-) L'esecuzione dell'intera suite dovrà avviare un bazillion di processi. Può essere un problema da gestire.
    • (-) Il sovraccarico dell'avvio del processo sarà visibile
  • L'altro lato sarebbe avere - ancora - un file cpp per test, ma tutti i file cpp di test (insieme al codice che testano!) Sono collegati in un eseguibile (per modulo / per progetto / scegli la tua scelta).
    • (+) Il tempo di compilazione sarebbe ancora OK, poiché verrà compilato solo il codice modificato.
    • (+) Eseguire l'intera suite è facile, poiché c'è solo un exe da eseguire.
    • (-) La suite impiegherà anni per il collegamento, poiché ogni ricompilazione di qualsiasi oggetto attiverà un nuovo collegamento.
    • (-) (?) La tuta impiegherà più tempo a correre, anche se se tutti i test delle unità sono veloci, il tempo dovrebbe essere OK.

Quindi, come vengono gestiti i test unitari C ++ nel mondo reale ? Se eseguo solo quella roba ogni notte / ogni ora, la seconda parte non ha molta importanza, ma la prima parte, vale a dire come "accoppiare" il codice UT al codice di produzione, in modo che sia "naturale" per gli sviluppatori mantenere entrambi in la messa a fuoco conta sempre, penso. (E se gli sviluppatori hanno a fuoco il codice UT, vorranno eseguirlo, il che ci riporta alla seconda parte.)

Storie ed esperienze del mondo reale apprezzate!

Appunti:

  • Questa domanda lascia intenzionalmente la piattaforma non specificata e crea / progetta il sistema.
  • Domande contrassegnate UT & C ++ è un ottimo punto di partenza, ma purtroppo troppe domande, e in particolare le risposte, sono troppo focalizzate sui dettagli o su quadri specifici.
  • Qualche tempo fa, ho risposto a una domanda simile sulla struttura per i test delle unità di boost. Trovo che questa struttura sia carente per "veri", veloci test unitari. E trovo l'altra domanda troppo ristretta, quindi questa nuova domanda.

6
Un'ulteriore complicazione è introdotta dal linguaggio C ++ di spostare il maggior numero possibile di errori per compilare il tempo: una buona suite di unit test spesso deve essere in grado di verificare che un determinato utilizzo non riesca a compilare.

3
@Closers: Potresti per favore fornire gli argomenti che ti hanno portato a chiudere questa domanda?
Luc Touraille,

Non capisco bene perché questo debba essere chiuso. :-(Dove pensi di cercare risposte a tali domande se non in questo forum?

2
@Joe: Perché ho bisogno di un unit test per scoprire se qualcosa verrà compilato quando il compilatore me lo dirà comunque?
David Thornley,

2
@ David: Perché vuoi assicurarti che non si compili . Un esempio rapido potrebbe essere un oggetto pipeline che accetta un input e produce output che Pipeline<A,B>.connect(Pipeline<B,C>)dovrebbe essere compilato mentre Pipeline<A,B>.connect(Pipeline<C,D>)non dovrebbe essere compilato: il tipo di output del primo stadio è incompatibile con il tipo di input del secondo stadio.
sebastiangeiger,

Risposte:


6

Abbiamo tutti i test unitari (per un modulo) in un eseguibile. I test sono suddivisi in gruppi. Posso eseguire un singolo test (o alcuni test) o un gruppo di test specificando un nome (test / gruppo) sulla riga di comando del test runner. Il sistema di build può eseguire il gruppo "Build", il reparto test può eseguire "All". Lo sviluppatore può mettere alcuni test in un gruppo come "BUG1234" con 1234 come numero di tracker del problema su cui sta lavorando.


6

Innanzitutto, non sono d'accordo con "1) Modifica il tuo codice (di produzione) e il tuo test unitario". Dovresti modificarne solo uno alla volta, altrimenti se il risultato cambia non saprai quale lo ha causato.

Mi piace mettere i test unitari in un albero di directory che ombreggia l'albero principale. Se ho /sources/componentA/alpha/foo.cce /objects/componentA/beta/foo.o, allora voglio qualcosa di simile /UTest_sources/componentA/alpha/test_foo.cce /UTest_objects/componentA/beta/test_foo.o. Uso lo stesso albero delle ombre per oggetti stub / finti e qualsiasi altra fonte di cui i test necessitino. Ci saranno alcuni casi limite, ma questo schema semplifica molto le cose. Una buona macro dell'editor può estrarre facilmente la sorgente di test insieme alla fonte del soggetto. Un buon sistema di compilazione (ad esempio GNUMake) può compilare entrambi ed eseguire il test con un solo comando (ad esempio make test_foo) e può gestire un bazillion di tali processi - solo quelli le cui fonti sono cambiate dall'ultima volta che sono stati testati - abbastanza facilmente (ho mai trovato l'overhead di avviare questi processi per essere un problema, è O (N)).

Nello stesso framework, puoi avere test su larga scala (non più unit test) che collegano molti oggetti insieme ed eseguono molti test. Il trucco è ordinare questi test in base al tempo impiegato per la compilazione / esecuzione e inserirli di conseguenza nel programma giornaliero. Esegui il test di un secondo o meno ogni volta che ne hai voglia; avviare il test di dieci secondi e allungare; test di cinque minuti e fare una pausa; mezz'ora di prova e andare a pranzo; test di sei ore e torna a casa. Se ti accorgi che stai perdendo molto tempo, ad esempio ricollegando un test enorme dopo aver modificato solo un file di piccole dimensioni, stai sbagliando-- anche se il collegamento fosse istantaneo, avresti comunque eseguito un lungo test quando non era richiesto.


annuncio (1) - sì, che è stato formulato in modo sciatto
Martin Ba
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.