Unit testing C ++: cosa testare?


20

TL; DR

Scrivere buoni test utili è difficile e ha un costo elevato in C ++. Riesci a sviluppare sviluppatori che condividono la tua logica su cosa e quando testare?

Lunga storia

Ero solito fare uno sviluppo guidato dai test, in effetti tutto il mio team, ma per noi non ha funzionato bene. Abbiamo molti test, ma non sembrano mai coprire i casi in cui abbiamo veri e propri bug e regressioni - che di solito si verificano quando le unità interagiscono, non dal loro comportamento isolato.

Questo è spesso così difficile da testare a livello di unità che abbiamo smesso di fare TDD (ad eccezione dei componenti in cui accelera davvero lo sviluppo) e invece abbiamo investito più tempo per aumentare la copertura dei test di integrazione. Mentre i test sulle unità di piccole dimensioni non hanno mai individuato errori reali e sono stati sostanzialmente solo un sovraccarico di manutenzione, i test di integrazione hanno davvero valso la pena.

Ora ho ereditato un nuovo progetto e mi chiedo come procedere per testarlo. È un'applicazione nativa C ++ / OpenGL, quindi i test di integrazione non sono in realtà un'opzione. Ma i test unitari in C ++ sono un po 'più difficili che in Java (devi fare esplicitamente cose virtual), e il programma non è fortemente orientato agli oggetti, quindi non riesco a deridere / nascondere alcune cose.

Non voglio fare a pezzi e OO-ize il tutto solo per scrivere alcuni test per il gusto di scrivere test. Quindi ti sto chiedendo: per cosa dovrei scrivere i test? per esempio:

  • Funzioni / Classi che mi aspetto di cambiare frequentemente?
  • Funzioni / Classi che sono più difficili da testare manualmente?
  • Funzioni / Classi che sono già facili da testare?

Ho iniziato a studiare alcune basi di codice C ++ rispettose per vedere come vanno i test. In questo momento sto esaminando il codice sorgente di Chromium, ma sto trovando difficile estrarre la logica del test dal codice. Se qualcuno ha un buon esempio o post su come gli utenti C ++ popolari (ragazzi del comitato, autori di libri, Google, Facebook, Microsoft, ...) si avvicinano a questo, sarebbe molto utile.

Aggiornare

Mi sono cercato su questo sito e sul web da quando ho scritto questo. Ho trovato delle cose buone:

Purtroppo, tutti questi sono piuttosto centrati su Java / C #. Scrivere molti test in Java / C # non è un grosso problema, quindi il vantaggio di solito supera i costi.

Ma come ho scritto sopra, è più difficile in C ++. Soprattutto se la tua base di codice non è così-OO, devi fare un sacco di cose per ottenere una buona copertura del test unitario. Ad esempio: l'applicazione che ho ereditato ha uno Graphicsspazio dei nomi che è un livello sottile sopra OpenGL. Per testare una qualsiasi delle entità - che usano tutte direttamente le sue funzioni - dovrei trasformarla in un'interfaccia e una classe e iniettarla in tutte le entità. Questo è solo un esempio.

Quindi, quando rispondi a questa domanda, tieni presente che devo fare un investimento piuttosto grande per scrivere test.


3
+1 per la difficoltà nel test unitario C ++. Se il test dell'unità richiede di modificare il codice, non farlo.
DPD,

2
@DPD: Non ne sono così sicuro, cosa succede se qualcosa vale davvero la pena testare? Nell'attuale base di codice, difficilmente riesco a testare nulla nel codice di simulazione perché tutto chiama direttamente le funzioni grafiche e non posso deriderle / stub. Tutto ciò che posso testare in questo momento sono funzioni di utilità. Ma sono d'accordo, cambiare il codice per renderlo "testabile" sembra ... sbagliato. I sostenitori di TDD spesso affermano che questo renderà tutto il tuo codice migliore in tutti i modi immaginabili, ma umilmente non sono d'accordo. Non tutto richiede un'interfaccia e diverse implementazioni.
futlib

Lasciate che vi faccia un esempio recente: ho passato un'intera giornata a provare una funzione singe (scritta in C ++ / CLI) e lo strumento di test MS Test si arrestava sempre in modo anomalo per questo test. Sembrava avere qualche problema con semplici riferimenti CPP. Invece ho appena provato l'output della sua funzione di chiamata e ha funzionato bene. Ho sprecato un'intera giornata in una funzione UT. È stata una perdita di tempo prezioso. Inoltre non sono riuscito a ottenere alcun attrezzo di mozziconi adatto alle mie esigenze. Ho fatto il mozzicone manuale ove possibile.
DPD,

Questo è proprio il tipo di cose che vorrei evitare: DI suppongo che gli sviluppatori C ++ debbano essere particolarmente pragmatici nei test. Hai finito per testarlo, quindi credo che sia OK.
futlib

@DPD: Ci ho pensato un po 'di più e penso che tu abbia ragione, la domanda è: che tipo di compromesso voglio fare. Vale la pena refactoring dell'intero sistema grafico per testare un paio di entità? Non c'era nessun bug lì dentro che conosco, quindi probabilmente: No. Se inizia a sentirsi difettoso, scriverò dei test. Peccato che non posso accettare la tua risposta perché è un commento :)
futlib

Risposte:


5

Bene, Unit Testing è solo una parte. I test di integrazione ti aiutano a risolvere il problema del tuo team. I test di integrazione possono essere scritti per tutti i tipi di applicazioni, anche per applicazioni native e OpenGL. Dovresti dare un'occhiata a "Software orientato agli oggetti in crescita guidato da test" di Steve Freemann e Nat Pryce (ad es. Http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627 ). Ti guida passo dopo passo nello sviluppo di un'applicazione con interfaccia grafica e comunicazione di rete.

Testare un software che non è stato testato è un'altra storia. Controlla Michael Feathers "Lavorare efficacemente con il codice legacy" (http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052).


Conosco entrambi i libri. Le cose sono: 1. Non vogliamo andare con TDD, perché non ha funzionato bene con noi. Vogliamo test, ma non religiosamente. 2. Sono sicuro che i test di integrazione per le applicazioni OpenGL sono possibili in qualche modo, ma ci vuole troppo sforzo. Voglio continuare a migliorare l'app, non avviare un progetto di ricerca.
futlib

Tenere presente che il test dell'unità "dopo il fatto" è sempre più difficile del "test prima", poiché il codice non viene progettato per essere testabile (riutilizzabile, mantenibile, ecc.). Se vuoi farlo comunque, prova a seguire i trucchi di Michael Feathers (es. Cuciture) anche se eviti il ​​TDD. Ad esempio, se si desidera estendere una funzione, provare qualcosa come "metodo sprout" e provare a mantenere testabile il nuovo metodo. È possibile farlo, ma IMHO più difficile.
EricSchaefer,

Concordo sul fatto che il codice non TDD non è progettato per essere testabile, ma non direi che non è gestibile o riutilizzabile di per sé - come ho già detto in precedenza, alcune cose non richiedono interfacce e implementazioni multiple. Non è affatto un problema con Mockito, ma in C ++, devo rendere virtuali tutte le funzioni che voglio stub / deridere. Ad ogni modo, il codice non testabile è il mio problema più grande in questo momento: devo cambiare alcune cose fondamentali per rendere alcune parti testabili, e quindi voglio una buona logica su cosa testare, per assicurarmi che ne valga la pena.
futlib

Hai ragione ovviamente, starò attento a rendere testabile qualsiasi nuovo codice che scrivo. Ma non sarà facile, con il modo in cui le cose funzionano in questa base di codice in questo momento.
futlib

Quando aggiungi una caratteristica / funzione, pensa solo a come puoi provarla. Potresti iniettare delle brutte dipendenze? Come faresti a sapere che la funzione fa quello che dovrebbe fare. Potresti osservare qualche comportamento? C'è qualche risultato che potresti verificare per correttezza? Ci sono invarianti che puoi controllare?
EricSchaefer,

2

È un peccato TDD "non ha funzionato bene per te". Penso che sia la chiave per capire dove rivolgersi. Rivedi e capisci come il TDD non ha funzionato, cosa avresti potuto fare meglio, perché c'era difficoltà.

Quindi, ovviamente i tuoi test unitari non hanno rilevato i bug che hai trovato. Questo è il punto. :-) Non hai trovato questi bug perché hai impedito che si verificassero in primo luogo pensando a come dovrebbero funzionare le interfacce e come assicurarsi che siano state testate correttamente.

Per rispondere, si pone una domanda, come si è concluso, un codice di test unitario che non è progettato per essere testato è difficile. Per il codice esistente potrebbe essere più efficace utilizzare un ambiente di test funzionale o di integrazione piuttosto che un ambiente di test unitario. Testare il sistema in generale concentrandosi su aree specifiche.

Naturalmente il nuovo sviluppo trarrà beneficio dal TDD. Con l'aggiunta di nuove funzionalità, il refactoring per TDD potrebbe aiutare a testare il nuovo sviluppo, consentendo allo stesso tempo lo sviluppo di un nuovo test unitario per le funzioni legacy.


4
Abbiamo fatto TDD per circa un anno e mezzo, tutti abbastanza appassionati. Tuttavia, confrontando i progetti TDD con quelli precedenti realizzati senza TDD (ma non senza test), non direi che in realtà sono più stabili o hanno un codice meglio progettato. Forse è il nostro team: ci accoppiamo e recensiamo molto, la nostra qualità del codice è sempre stata abbastanza buona.
futlib

1
Più ci penso, più penso che TDD non si adattava molto bene alla tecnologia di quel particolare progetto: Flex / Swiz. Sono in corso molti eventi, associazioni e iniezioni che rendono complicate le interazioni tra oggetti e quasi impossibili da testare. Il disaccoppiamento di quegli oggetti non lo rende migliore, perché funzionano correttamente in primo luogo.
futlib

2

Non ho fatto TDD in C ++, quindi non posso commentarlo, ma dovresti testare il comportamento previsto del tuo codice. Mentre l'implementazione può cambiare, il comportamento dovrebbe (di solito?) Rimanere lo stesso. Nel mondo incentrato su Java \ C #, ciò significherebbe testare solo i metodi pubblici, scrivere test per il comportamento previsto e farlo prima dell'implementazione (che di solito è meglio dire che fare :)).

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.