C'è un motivo per cui i test non sono scritti in linea con il codice che testano?

90

Recentemente ho letto un po 'di Literate Programming , e mi ha fatto pensare ... Ben scritta test, in particolare le specifiche in stile BDD possono fare un lavoro migliore per spiegare cosa fa il codice rispetto alla prosa, e hanno il grande vantaggio di verificare la propria accuratezza.

Non ho mai visto test scritti in linea con il codice che testano. Ciò è dovuto al fatto che le lingue non tendono a semplificare la separazione del codice dell'applicazione e del test quando vengono scritte nello stesso file sorgente (e nessuno lo ha reso semplice) oppure esiste un motivo principale per cui le persone separano il codice di test dal codice dell'applicazione?

    
posta Chris Devereux 25.02.2013 - 14:28
fonte

12 risposte

89

L'unico vantaggio che posso pensare per i test in linea sarebbe la riduzione del numero di file da scrivere. Con gli IDE moderni questo non è un vero affare.

Esistono tuttavia alcuni inconvenienti ovvi per i test in linea:

  • Violare separazione delle preoccupazioni . Questo può essere discutibile, ma per me testare la funzionalità è una responsabilità diversa dall'implementarlo.
  • Dovresti introdurre nuove funzionalità linguistiche per distinguere tra test / implementazione, altrimenti rischieresti di sfumare la linea tra i due.
  • I file sorgente di maggiori dimensioni sono più difficili da utilizzare: più difficile da leggere, più difficile da comprendere, è più probabile che tu debba affrontare i conflitti di controllo del codice sorgente.
  • Penso che renderebbe più difficile mettere il tuo cappello "tester", per così dire. Se stai osservando i dettagli dell'implementazione, sarai tentato di saltare l'implementazione di alcuni test.
risposta data 25.02.2013 - 15:15
fonte
36

Posso pensare ad alcuni:

  • La leggibilità. Intersperser codice "reale" e test renderà più difficile leggere il codice reale.

  • Codice gonfiato. Mescolando codice "reale" e codice di prova negli stessi file / classi / qualunque cosa risulti probabile in file compilati più grandi, ecc. Ciò è particolarmente importante per le lingue con l'associazione tardiva.

  • Potresti non volere che i tuoi clienti / clienti vedano il tuo codice di test. (Non ho mi piace questo motivo ... ma se stai lavorando su un progetto closed source, è improbabile che il codice di test aiuti comunque il cliente.)

Ora ci sono soluzioni alternative per ciascuno di questi problemi. Ma IMO, è più semplice non andare lì in primo luogo.

Vale la pena osservare che nei primi tempi, i programmatori Java facevano questo genere di cose; per esempio. incluso un metodo main(...) in una classe per facilitare il test. Questa idea è quasi completamente scomparsa. È prassi del settore implementare i test separatamente utilizzando un framework di test di qualche tipo.

Vale anche la pena di osservare che la programmazione letteraria (concepita da Knuth) non ha mai preso piede nel settore dell'ingegneria del software.

    
risposta data 25.02.2013 - 14:38
fonte
13

Per molte delle stesse ragioni per cui cerchi di evitare l'accoppiamento stretto tra le classi del tuo codice, è anche una buona idea evitare l'accoppiamento non necessario tra test e codice.

Creazione: test e codice possono essere scritti in momenti diversi, da persone diverse.

Controllo: se i test sono utilizzati per specificare i requisiti, vorresti che fossero soggetti a regole diverse su chi può cambiarli e quando il codice effettivo è.

Riusabilità: se inserisci i test in linea, non puoi utilizzarli con un'altra porzione di codice.

Immagina di avere un pezzo di codice che fa il lavoro correttamente, ma lascia molto a desiderare in termini di prestazioni, manutenibilità, qualsiasi cosa. Decidi di sostituire quel codice con codice nuovo e migliorato. L'uso della stessa serie di test può aiutarti a verificare che il nuovo codice produca gli stessi risultati del vecchio codice.

Selettività: Mantenere separati i test dal codice semplifica la scelta dei test da eseguire.

Ad esempio, potresti avere una piccola serie di test relativi al codice su cui stai attualmente lavorando e una suite più ampia che verifica l'intero progetto.

    
risposta data 25.02.2013 - 20:03
fonte
13

In realtà, puoi pensare a Design by Contract come a fare questo. Il problema è che la maggior parte dei linguaggi di programmazione non ti permette di scrivere codice come questo :( È molto facile testare a mano le condizioni preliminari, ma le condizioni di post sono una vera sfida senza cambiare il modo di scrivere codice (un IMO negativo enorme). / p>

Michael Feathers ha una presentazione su questo e questo è uno dei tanti modi in cui lui parla che puoi migliorare la qualità del codice.

    
risposta data 21.03.2013 - 19:43
fonte
10

Ecco alcuni motivi aggiuntivi a cui posso pensare:

  • avere test in una libreria separata rende più semplice collegare solo quella libreria al framework di test, e non il codice di produzione (questo potrebbe essere evitato da qualche preprocessore, ma perché costruire una cosa simile quando la soluzione più semplice è scrivere i test in un posto separato)

  • test di una funzione, una classe, una libreria sono tipicamente scritti da un punto di vista "utenti" (un utente di quella funzione / classe / libreria). Tale "codice d'uso" è tipicamente scritto in un file o una libreria separati, e un test può essere più chiaro o "più realistico" se imita quella situazione.

risposta data 25.02.2013 - 15:14
fonte
5

Se i test erano in linea, sarebbe necessario rimuovere il codice necessario per i test quando si spedisce il prodotto al cliente. Quindi, un punto in più in cui archiviare i test separa semplicemente tra il codice tu necessario e il codice richiesto dal tuo cliente .

    
risposta data 25.02.2013 - 14:38
fonte
4

Questo è in risposta a un gran numero di commenti che suggeriscono che i test in linea non vengono eseguiti perché è difficile da rimuovere il codice di test dalle build di rilascio. Questo non è vero. Quasi tutti i compilatori e gli assemblatori lo supportano già, con linguaggi compilati, come C, C ++, C #, questo viene fatto con quelle che vengono chiamate direttive del compilatore.

Nel caso di c # (credo anche in c ++, la sintassi potrebbe essere leggermente diversa a seconda del compilatore che stai usando) questo è come puoi farlo.

#define DEBUG //  = true if c++ code
#define TEST /* can also be defined in the make file for c++ or project file for c# and applies to all associated .cs/.cpp files */

//somewhere in your code
#if DEBUG
// debug only code
#elif TEST
// test only code
#endif

Poiché utilizza direttive del compilatore, il codice non esiste nei file eseguibili creati se i flag non sono impostati. Questo è anche il modo in cui crei programmi "scrivi una volta, compila due volte" per più piattaforme / hardware.

    
risposta data 04.05.2013 - 23:02
fonte
3

Questa idea equivale semplicemente a un metodo "Self_Test" nel contesto di un progetto basato su oggetti o orientato agli oggetti. Se si utilizza un linguaggio basato su oggetti compilato come Ada, tutto il codice di auto-test verrà contrassegnato dal compilatore come non usato (mai invocato) durante la compilazione di produzione, e quindi sarà tutto ottimizzato - nessuno di questi apparirà nel risultante eseguibile.

L'utilizzo di un metodo "Self_Test" è un'ottima idea e, se i programmatori fossero davvero interessati alla qualità, lo farebbero tutti. Un problema importante, tuttavia, è che il metodo "Self_Test" deve avere una disciplina intensa, in quanto non può accedere a nessuno dei dettagli di implementazione e deve invece fare affidamento solo su tutti gli altri metodi pubblicati all'interno delle specifiche dell'oggetto. Ovviamente, se l'autotest fallisce, l'implementazione dovrà cambiare. L'autotest dovrebbe testare rigorosamente tutte le proprietà pubblicate dei metodi dell'oggetto, ma non fare mai affidamento in alcun modo sui dettagli di qualsiasi implementazione specifica.

I linguaggi object-based e object-oriented forniscono frequentemente quel tipo di disciplina rispetto ai metodi esterni all'oggetto testato (applicano la specifica dell'oggetto, impedendo qualsiasi accesso ai dettagli dell'implementazione e sollevando un errore di compilazione, se tale tentativo è rilevato). Ma tutti i metodi interni dell'oggetto hanno accesso completo a tutti i dettagli di implementazione. Quindi il metodo di autotest si trova in una situazione unica: deve essere un metodo interno a causa della sua natura (l'autotest è ovviamente un metodo dell'oggetto testato), ma deve ricevere tutta la disciplina del compilatore di un metodo esterno ( deve essere indipendente dai dettagli di implementazione dell'oggetto). Pochi, se qualche linguaggio di programmazione fornisce la capacità di disciplinare il metodo interno di un oggetto come se fosse un metodo esterno. Quindi questo è un importante problema di progettazione del linguaggio di programmazione.

In assenza di un adeguato supporto per il linguaggio di programmazione, il modo migliore per farlo è creare un oggetto associato. In altre parole, per ogni oggetto che codi (chiamiamolo "Big_Object"), crei anche un secondo oggetto companion il cui nome consiste in un suffisso standard concatenato con il nome dell'oggetto "reale" (in questo caso, "Big_Object_Self_Test "), e la cui specifica consiste in un singolo metodo (" Big_Object_Self_Test.Self_Test (This_Big_Object: Big_Object) return Boolean; "). L'oggetto associato dipenderà quindi dalla specifica dell'oggetto principale e il compilatore applicherà completamente tutta la disciplina di tale specifica rispetto all'implementazione dell'oggetto associato.

    
risposta data 04.05.2013 - 19:01
fonte
2

Usiamo test in linea con il nostro codice Perl. C'è un modulo, Test :: Inline , che genera file di test dal codice inline.

Non sono particolarmente bravo nell'organizzare i miei test e li ho trovati più facili e più probabilmente mantenuti in linea.

Rispondendo a un paio delle preoccupazioni sollevate:

  • I test inline sono scritti nelle sezioni POD, quindi non fanno parte del codice reale. Vengono ignorati dall'interprete, quindi non c'è un codice gonfio.
  • Utilizziamo Vim folding per nascondere le sezioni di test. L'unica cosa che vedi è una singola riga sopra ogni metodo testato come +-- 33 lines: #test---- . Quando vuoi lavorare con il test, lo espandi.
  • Il modulo Test :: Inline "compila" i test sui normali file compatibili con TAP, in modo che possano coesistere con i test tradizionali.

Per riferimento:

risposta data 04.05.2013 - 23:04
fonte
1

Erlang 2 supporta effettivamente i test in linea. Qualsiasi espressione booleana nel codice che non viene utilizzata (ad esempio, assegnata a una variabile o passata) viene automaticamente considerata come un test e valutata dal compilatore; se l'espressione è falsa, il codice non viene compilato.

    
risposta data 04.05.2013 - 21:38
fonte
1

Un altro motivo per separare i test è che spesso si utilizzano librerie aggiuntive o addirittura diverse per i test piuttosto che per l'implementazione effettiva. Se si mischiano test e implementazione, il compilatore non può rilevare l'utilizzo accidentale delle librerie di test nell'implementazione.

Inoltre, i test tendono ad avere più linee di codice rispetto alle parti di implementazione testate, quindi avrai difficoltà a trovare l'implementazione tra tutti i test. : -)

    
risposta data 04.06.2013 - 10:52
fonte
0

Questo non è vero. È molto meglio posizionare i test unitari sul lato del codice di produzione quando il codice di produzione è particolarmente quando la routine di produzione è pura.

Se stai sviluppando sotto .NET, ad esempio, puoi inserire il codice di test nell'assembly di produzione e quindi utilizzare Scalpel per rimuoverli prima della spedizione.

    
risposta data 06.05.2016 - 23:07
fonte

Leggi altre domande sui tag