Domanda

Ho appena iniziato a scrivere unit test per un modulo di codice legacy con grandi dipendenze fisiche utilizzando la direttiva #include.Li ho affrontati in alcuni modi che sembravano eccessivamente noiosi (fornendo intestazioni vuote per interrompere lunghi elenchi di dipendenze #include e utilizzando #define per impedire la compilazione delle classi) e stavo cercando alcune strategie migliori per gestire questi problemi.

Mi sono spesso imbattuto nel problema di duplicare quasi tutti i file di intestazione con una versione vuota per separare la classe che sto testando nella sua interezza, e quindi scrivere un sostanziale codice stub/mock/falso per gli oggetti che dovranno essere sostituiti poiché ora non sono definiti.

Qualcuno conosce qualche pratica migliore?

È stato utile?

Soluzione

La depressione nelle risposte è schiacciante...Ma non temere, ce l'abbiamo il libro sacro per esorcizzare i demoni del codice C++ legacy.Seriamente, acquista il libro se sei in fila per più di una settimana a giocare con il codice C++ legacy.

Vai a pagina 127: Il caso dell'orribile include le dipendenze. (Ora non sono nemmeno a pochi chilometri da Michael Feathers, ma ecco la risposta più breve che potrei gestire..)

Problema:In C++ se una classe A ha bisogno di sapere della Classe B, la dichiarazione della Classe B viene direttamente sollevata/inclusa testualmente nel file sorgente della Classe A.E poiché a noi programmatori piace portarlo all'estremo sbagliato, un file può includere ricorsivamente un'infinità di altri file in modo transitivo.Le costruzioni richiedono anni..ma ehi almeno costruisce..possiamo aspettare.

Ora dire che "istanziare la Classe A con un sistema di test è difficile" è un eufemismo.(Citando l'esempio di MF: Scheduler è il nostro problema con i poster con deps in abbondanza.)

#include "TestHarness.h"
#include "Scheduler.h"
TEST(create, Scheduler)     // your fave C++ test framework macro
{
  Scheduler scheduler("fred");
}

Questo farà emergere il drago include con una raffica di errori di compilazione.
Colpo n. 1 Pazienza e persistenza:Affronta ciascuna inclusione una alla volta e decidi se abbiamo davvero bisogno di quella dipendenza.Supponiamo che SchedulerDisplay sia uno di questi, il cui metodo displayEntry viene chiamato nel ctor di Scheduler.
Soffio n. 2 Fingi finché non ce la fai (Grazie RonJ):

#include "TestHarness.h"
#include "Scheduler.h"
void SchedulerDisplay::displayEntry(const string& entryDescription) {}
TEST(create, Scheduler)
{
  Scheduler scheduler("fred");
}

E il pop va alla dipendenza e a tutte le sue include transitive.Puoi anche riutilizzare i metodi Fake incapsulandoli in un file Fakes.h da includere nei file di test.
Pratica del colpo n. 3:Potrebbe non essere sempre così semplice..ma hai capito.Dopo i primi duelli, il processo di rottura dei livelli diventerà semplice e meccanico

Avvertenze (Ho già detto che ci sono avvertenze?:)

  • Abbiamo bisogno di una build separata per i casi di test in questo file;possiamo avere solo 1 definizione per il metodo SchedulerDisplay::displayEntry in un programma.Quindi crea un programma separato per i test dello scheduler.
  • Non stiamo interrompendo alcuna dipendenza nel programma, quindi non stiamo rendendo il codice più pulito.
  • Devi mantenere quei falsi finché avremo bisogno dei test.
  • Il tuo senso estetico potrebbe essere offeso per un po’..morditi semplicemente il labbro e "sopportaci per un domani migliore"

Utilizzare questa tecnica per una classe molto numerosa con gravi problemi di dipendenza.Non usare spesso o leggermente.. Utilizzare questo come punto di partenza per refactoring più profondi. Nel corso del tempo questo programma di test può essere portato dietro la stalla mentre estrai più classi (CON i propri test).

Per più..per favore leggi il libro.Inestimabile.Lotta su fratello!

Altri suggerimenti

Dato che stai testando il codice legacy, presumo che non sia possibile eseguire il refactoring di detto codice per avere meno dipendenze (ad es.utilizzando il idioma brufoloso)

Questo ti lascia con poche opzioni, temo.Ogni intestazione inclusa per un tipo o una funzione avrà bisogno di un oggetto fittizio per quel tipo o funzione affinché tutto possa essere compilato, c'è poco che puoi fare...

Non sto rispondendo direttamente alla tua domanda, ma temo che i test unitari potrebbero non essere la cosa da fare se lavori con grandi quantità di codice legacy.

Dopo aver guidato un team XP su un progetto di sviluppo green field, ho adorato i miei test unitari.Sono successe delle cose e qualche anno dopo mi sono ritrovato a lavorare su una vasta base di codice legacy che presenta molti problemi di qualità.

Ho provato a trovare un modo per aggiungere unit test all'applicazione ma alla fine sono rimasto bloccato in un catch-22:

  1. Per scrivere unit test completi e significativi, il codice dovrebbe essere sottoposto a refactoring.
  2. Senza test unitari sarebbe troppo pericoloso rifattorizzare il codice.

Se ti senti un eroe e bevi il fantastico aiuto durante i test unitari, puoi comunque provarlo, ma c'è il rischio reale di ritrovarti con solo più codice di test di scarso valore che ora deve essere mantenuto.

A volte è semplicemente meglio lavorare sul codice nel modo in cui è "progettato" per essere lavorato.

Non so se questo funzionerà per il tuo progetto, ma potresti provare ad attaccare il problema dal fase di collegamento della tua corporatura.

Ciò eliminerebbe completamente il tuo problema #include.Tutto quello che dovresti fare è reimplementare le interfacce nei file inclusi per fare quello che vuoi e poi semplicemente collegarti ai file oggetto fittizi che hai creato per implementare le interfacce nel file di inclusione.

Il grande svantaggio di questo metodo è un sistema di compilazione più completo.

Se continui a scrivere stub/codici fittizi/falsi rischi di eseguire test unitari su una classe che ha un comportamento diverso rispetto a quando compilato nel progetto principale.

Ma se questi include sono presenti e non hanno alcun comportamento aggiuntivo, allora va bene.

Proverei a non modificare nulla sugli include durante l'esecuzione dei test unitari, quindi sei sicuro (per quanto puoi essere sul codice legacy :)) di testare il codice reale.

Sei decisamente tra l'incudine e il martello con codice legacy con grandi dipendenze.Ti aspetta un lungo duro lavoro per risolvere tutto.

Da quello che dici, sembra che tu stia cercando di mantenere intatto il codice sorgente per ciascun modulo a turno, inserendolo in un test con dipendenze esterne derise.Il mio suggerimento qui sarebbe quello di fare il passo ancora più coraggioso di tentare qualche refactoring per eliminare (o invertire) le dipendenze, che è probabilmente proprio il passaggio che stai cercando di evitare.

Lo suggerisco perché immagino che le dipendenze ti uccideranno mentre scrivi i test.Sicuramente starai meglio a lungo termine se riesci a eliminare le dipendenze.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top