Testing vs Do not Repeat YYLY (DRY)

10

Perché ripetersi scrivendo test così altamente incoraggiati?

Sembra che i test esprimano fondamentalmente la stessa cosa del codice, e quindi è un duplicato (nel concetto, non nell'implementazione) del codice. L'obiettivo finale di DRY non sarebbe l'eliminazione di tutti i codici di test?

    
posta John Tseng 30.01.2014 - 06:24
fonte

10 risposte

23

Credo che sia un malinteso qualunque sia il mio modo di pensare.

Il codice di test che testa il codice di produzione non è affatto simile. Dimostrerò in python:

def multiply(a, b):
    """Multiply ''a'' by ''b''"""
    return a*b

Quindi un semplice test sarebbe:

def test_multiply():
    assert multiply(4, 5) == 20

Entrambe le funzioni hanno una definizione simile ma entrambe fanno cose molto diverse. Nessun codice duplicato qui. ; -)

Si verifica inoltre che le persone scrivano test duplicati essenzialmente con un'asserzione per funzione di test. Questa è follia e ho visto persone farlo. Questa è cattiva pratica.

def test_multiply_1_and_3():
    """Assert that a multiplication of 1 and 3 is 3."""
    assert multiply(1, 3) == 3

def test_multiply_1_and_7():
    """Assert that a multiplication of 1 and 7 is 7."""
    assert multiply(1, 7) == 7

def test_multiply_3_and_4():
    """Assert that a multiplication of 3 and 4 is 12."""
    assert multiply(3, 4) == 12

Immagina di farlo per oltre 1000 linee di codice efficaci. Invece si prova su una base per 'funzionalità':

def test_multiply_positive():
    """Assert that positive numbers can be multiplied."""
    assert multiply(1, 3) == 3
    assert multiply(1, 7) == 7
    assert multiply(3, 4) == 12

def test_multiply_negative():
    """Assert that negative numbers can be multiplied."""
    assert multiply(1, -3) == -3
    assert multiply(-1, -7) == 7
    assert multiply(-3, 4) == -12

Ora, quando le funzionalità vengono aggiunte / rimosse, devo solo considerare di aggiungere / rimuovere una funzione di test.

Potresti aver notato che non ho applicato for loops. Questo perché ripetere alcune cose va bene. Quando avrei applicato loops il codice sarebbe molto più breve. Ma quando un'affermazione fallisce potrebbe offuscare l'output mostrando un messaggio ambiguo. Se questo si verifica, i tuoi test saranno meno utili e tu vorrà avere bisogno di un debugger per ispezionare dove le cose vanno male.

    
risposta data 30.01.2014 - 07:43
fonte
9

It seems that tests basically express the same thing as the code, and hence is a duplicate

No, questo non è vero.

I test hanno uno scopo diverso dalla tua implementazione:

  • I test assicurano che la tua implementazione funzioni.
  • Servono come documentazione: osservando i test, vedi i contratti che il tuo codice deve soddisfare, cioè quale input restituisce quale output, quali sono i casi speciali ecc.
  • Inoltre, i test garantiscono che non appena si aggiungono nuove funzionalità, la funzionalità esistente non si interrompa.
risposta data 30.01.2014 - 07:02
fonte
4

No. DRY riguarda la scrittura di codice solo una volta per eseguire un determinato compito, test è la convalida che l'attività viene eseguita correttamente. È in qualche modo simile all'algoritmo di voto, dove ovviamente usare lo stesso codice sarebbe inutile.

    
risposta data 30.01.2014 - 07:20
fonte
2

Wouldn't the ultimate target of DRY include elimination of all test code?

No, l'obiettivo finale di DRY in realtà significherebbe l'eliminazione di tutti i codice di produzione .

Se i nostri test potrebbero essere specifiche perfette di ciò che vogliamo che il sistema faccia, dovremmo semplicemente generare il corrispondente codice di produzione (o binari) automaticamente, rimuovendo efficacemente la base del codice di produzione di per sé.

Questo è in realtà ciò che approcci come l'architettura model-driven pretende di ottenere - una singola fonte di verità progettata dall'uomo da cui tutto è derivato dal calcolo.

Non penso che il contrario (eliminando tutti i test) sia desiderabile perché:

  • Devi risolvere il disadattamento di impedenza tra implementazione e specifica. Il codice di produzione può trasmettere l'intento in una certa misura, ma non sarà mai così facile ragionare su test ben espressi. Noi esseri umani abbiamo bisogno di una visione più elevata di perché stiamo costruendo cose. Anche se non esegui test a causa di DRY, le specifiche dovranno probabilmente essere scritte nei documenti comunque, che è una bestia decisamente più pericolosa in termini di mancata corrispondenza dell'impedenza e desincronizzazione del codice se me lo chiedi.
  • Mentre il codice di produzione è discutibilmente facilmente derivabile da specifiche eseguibili corrette (assumendo un tempo sufficiente), una suite di test è molto più difficile da ricostituire dal codice finale di un programma. Le specifiche non appaiono chiaramente solo guardando il codice, perché le interazioni tra le unità di codice in fase di esecuzione sono difficili da individuare. Questo è il motivo per cui abbiamo difficoltà a gestire le applicazioni legacy prive di test. In altre parole: se vuoi che la tua applicazione sopravviva per più di qualche mese, probabilmente dovresti perdere il disco rigido che ospita la base di codice di produzione rispetto a quella in cui si trova la tua suite di test.
  • È molto più facile introdurre un bug per errore nel codice di produzione che nel codice di test. E poiché il codice di produzione non è auto-verificante (sebbene possa essere affrontato con Design by Contract o sistemi di tipo più ricco), abbiamo ancora bisogno di un programma esterno per testarlo e avvisarci se si verifica una regressione.
risposta data 30.01.2014 - 14:00
fonte
1

Perché a volte ripetere te stesso va bene. Nessuno di questi principi deve essere preso in ogni circostanza senza domande o contesto. A volte ho scritto prove contro una versione ingenua (e lenta) di un algoritmo, che è una violazione abbastanza chiara di DRY, ma decisamente vantaggiosa.

    
risposta data 30.01.2014 - 07:16
fonte
1

Poiché il test unitario riguarda il rendere più difficili le modifiche involontarie , a volte può anche rendere più difficili modifiche intenzionali . Questo fatto è in effetti correlato al principio DRY.

Ad esempio, se hai una funzione MyFunction che viene chiamata nel codice di produzione in un solo posto e scrivi 20 test unitari per esso, puoi facilmente avere 21 posizioni nel codice in cui viene chiamata quella funzione . Ora, quando devi cambiare la firma di MyFunction , o la semantica, o entrambi (perché alcuni requisiti cambiano), hai 21 posizioni da cambiare invece di una sola. E la ragione è in realtà una violazione del principio DRY: hai ripetuto (almeno) la stessa chiamata di funzione a MyFunction 21 volte.

L'approccio corretto per questo caso applica il principio DRY al tuo codice di test: quando scrivi 20 test unitari, incapsula le chiamate a MyFunction nei tuoi test unitari in poche funzioni di supporto (idealmente solo una) , che sono utilizzati dai 20 test unitari. Idealmente, ti ritroverai con solo due punti nel tuo codice che chiama MyFunction : uno dal tuo codice di produzione e uno dai tuoi test unitari. Quindi, quando devi modificare la firma di MyFunction più tardi, avrai solo pochi punti da cambiare nei tuoi test.

"Alcuni posti" sono ancora più di "un posto" (ciò che ottieni con i test di unità no ), ma i vantaggi di avere test unitari dovrebbero superare pesantemente il vantaggio di avere meno codice da cambiare (altrimenti stai facendo test di unità completamente sbagliati).

    
risposta data 30.01.2014 - 08:31
fonte
0

Una delle maggiori sfide nella creazione di software è quella di acquisire i requisiti; rispondere alla domanda, "cosa dovrebbe fare questo software?" il software ha bisogno di requisiti precisi per definire con precisione ciò che il sistema deve fare, ma quelli che definiscono le esigenze di sistemi e progetti software spesso includono persone che non hanno un background software o formale (matematica). La mancanza di rigore nella definizione dei requisiti ha obbligato lo sviluppo del software a trovare un modo per convalidare il software ai requisiti.

Il team di sviluppo si è trovato a tradurre la descrizione colloquiale di un progetto in requisiti più rigorosi. La disciplina di test si è coalizzata come punto di controllo per lo sviluppo del software, per colmare il divario tra ciò che un cliente dice di volere e ciò che il software capisce di volere. Sia gli sviluppatori di software che il team di controllo qualità / testano la comprensione delle specifiche (informali) e ciascuno (indipendentemente) scrive software o test per garantire che la loro comprensione sia conforme. Aggiunta di un'altra persona per comprendere i requisiti (imprecisi) aggiunti domande e prospettive diverse per migliorare ulteriormente la precisione dei requisiti.

Dato che c'è sempre stato un test di accettazione, è stato naturale ampliare il ruolo di test per scrivere test automatici e unitari. Il problema era che richiedeva ai programmatori di effettuare test, e quindi hai ridotto la prospettiva dall'assicurazione della qualità ai programmatori che facevano test.

Ciò detto, probabilmente stai facendo dei test sbagliati se i tuoi test differiscono poco dai programmi reali. Il suggerimento di Msdy sarebbe quello di concentrarsi maggiormente su cosa nei test e meno su come.

L'ironia è che, piuttosto che acquisire una specifica formale dei requisiti dalla descrizione colloquiale, l'industria ha scelto di implementare test dei punti come codice per automatizzare i test. Piuttosto che produrre requisiti formali a cui il software potrebbe essere costruito per rispondere, l'approccio adottato è stato quello di testare alcuni punti, piuttosto che l'approccio al software di costruzione utilizzando la logica formale. Questo è un compromesso, ma è stato abbastanza efficace e relativamente di successo.

    
risposta data 30.01.2014 - 20:11
fonte
0

Se ritieni che il tuo codice di prova sia troppo simile al tuo codice di implementazione, ciò potrebbe indicare che stai sfruttando eccessivamente un quadro di simulazione. Test di simulazione a livello troppo basso possono finire con l'impostazione di test che assomiglia molto al metodo testato. Prova a scrivere test di livello superiore che hanno meno probabilità di interrompersi se cambi la tua implementazione (so che può essere difficile, ma se riesci a gestirlo avrai come risultato una suite di test più utile).

    
risposta data 03.12.2014 - 13:15
fonte
0

I test unitari non dovrebbero includere una duplicazione del codice sotto test, come già notato.

Vorrei aggiungere, tuttavia, che i test unitari in genere non sono SECCHI quanto il codice di "produzione", perché la configurazione tende ad essere simile (ma non identica) ai test ... specialmente se si dispone di un numero significativo di dipendenze che si stai deridendo / fingendo.
Naturalmente è possibile refactoring questo tipo di cose in un metodo di installazione comune (o un insieme di metodi di installazione) ... ma ho trovato che quei metodi di installazione tendono ad avere liste di parametri lunghi e sono piuttosto fragili.

Quindi sii pragmatico. Se è possibile consolidare il codice di installazione senza compromettere la gestibilità, farlo assolutamente. Ma se l'alternativa è un insieme complesso e fragile di metodi di installazione, un po 'di ripetizione nei metodi di test è OK.

Un evangelista del TDD / BDD locale si esprime così:
"Il tuo codice di produzione dovrebbe essere ASCIUTTO, ma va bene che i tuoi test siano" umidi ".

    
risposta data 03.12.2014 - 14:55
fonte
0

It seems that tests basically express the same thing as the code, and hence is a duplicate (in concept, not implementation) of the code.

Questo non è vero, i test descrivono i casi d'uso, mentre il codice descrive un algoritmo che passa i casi d'uso, quindi che è più generale. Con TDD si inizia con la scrittura di casi d'uso (probabilmente basati sulla storia dell'utente) e successivamente si implementa il codice necessario per passare questi casi d'uso. Quindi scrivi un piccolo test, una piccola porzione di codice, dopodiché ti rifatti se necessario per sbarazzarti delle ripetizioni. Ecco come funziona.

Con i test possono esserci anche ripetizioni. Ad esempio puoi riutilizzare le fixture, il codice di generazione delle fixture, le asserzioni complicate, ecc ... Di solito faccio questo, per prevenire i bug nei test, ma di solito dimentico di testare prima se un test fallisce davvero, e può davvero rovinare la giornata , quando stai cercando il bug nel codice per mezz'ora e il test è sbagliato ... xD

    
risposta data 03.12.2014 - 23:22
fonte

Leggi altre domande sui tag