Dal punto di vista del TDD, sono una persona cattiva se eseguo un test su un endpoint attivo anziché su una simulazione?


16

Seguo religiosamente il TDD. I miei progetti in genere hanno una copertura dei test dell'85% o superiore, con casi di test significativi.

Faccio molto lavoro con HBase e l'interfaccia client principale, HTable, è una vera seccatura da deridere. Mi occorrono 3 o 4 volte più tempo per scrivere i test unitari di quanto non faccia per scrivere test che utilizzano un endpoint attivo.

So che, filosoficamente, i test che usano le beffe dovrebbero avere la priorità rispetto ai test che usano un endpoint dal vivo. Ma deridere HTable è un dolore serio, e non sono davvero sicuro che offra molti vantaggi rispetto ai test contro un'istanza HBase live.

Tutti nel mio team eseguono un'istanza HBase a nodo singolo sulla propria workstation e abbiamo istanze HBase a nodo singolo in esecuzione sui nostri box Jenkins, quindi non è un problema di disponibilità. I test degli endpoint dal vivo ovviamente richiedono più tempo per essere eseguiti rispetto ai test che utilizzano simulazioni, ma non ci interessa davvero.

In questo momento, scrivo test di endpoint dal vivo E test basati su simulazioni per tutte le mie classi. Mi piacerebbe abbandonare le derisioni, ma di conseguenza non voglio che la qualità diminuisca.

Che ne pensate?


8
L'endpoint live non è davvero un test unitario, vero? È un test di integrazione. Ma alla fine è probabilmente una questione di pragmatismo; puoi passare il tempo a scrivere beffe o passare il tempo a scrivere funzionalità o correggere bug.
Robert Harvey,

4
Ho sentito storie di persone che eliminano servizi di terze parti eseguendo un test unitario sul proprio codice ... che è stato collegato a un endpoint live. La limitazione della frequenza non è qualcosa che i test unitari in genere fanno o interessano.

14
Non sei una persona cattiva. Sei una brava persona a fare una brutta cosa.
Kyralessa,

15
Seguo religiosamente il TDD Forse è questo il problema? Non credo che nessuna di queste metodologie debba essere presa sul serio. ;)
FrustratedWithFormsDesigner

9
Seguire religiosamente il TDD significherebbe scartare il codice scoperto del 15%.
mouviciel,

Risposte:


23
  • La mia prima raccomandazione sarebbe di non prendere in giro i tipi che non possiedi . Hai detto che HTable è una vera seccatura da deridere - forse dovresti invece avvolgerlo in un adattatore che espone il 20% delle funzionalità di HTable di cui hai bisogno e deridere il wrapper dove necessario.

  • Detto questo, supponiamo che stiamo parlando di tipi che possiedi tutti. Se i tuoi test basati su simulazioni sono focalizzati su scenari di percorsi felici in cui tutto procede senza intoppi, non perderai nulla abbandonandoli perché probabilmente i tuoi test di integrazione stanno già testando gli stessi identici percorsi.

    Tuttavia, i test isolati diventano interessanti quando inizi a pensare a come il tuo sistema sotto test dovrebbe reagire a ogni piccola cosa che potrebbe accadere come definito nel contratto del suo collaboratore, indipendentemente dall'oggetto concreto reale con cui sta parlando. Fa parte di ciò che alcuni chiamano correttezza di base . Potrebbero esserci molti di quei piccoli casi e molte più combinazioni di essi. È qui che i test di integrazione iniziano a diventare pessimi mentre i test isolati rimarranno veloci e gestibili.

    Per essere più concreti, cosa succede se uno dei metodi dell'adattatore HTable restituisce un elenco vuoto? Cosa succede se restituisce null? Cosa succede se genera un'eccezione di connessione? Dovrebbe essere definito nel contratto dell'adattatore se una qualsiasi di queste cose potrebbe accadere, e uno qualsiasi dei suoi consumatori dovrebbe essere preparato ad affrontare queste situazioni , quindi la necessità di test per esse.

Per riassumere: non vedrai alcun declino della qualità rimuovendo i tuoi test basati su simulazioni se hanno testato esattamente le stesse cose dei tuoi test di integrazione . Tuttavia, provare a immaginare ulteriori test isolati (e test di contratto ) può aiutarti a pensare ampiamente alle tue interfacce / contratti e ad aumentare la qualità affrontando difetti che sarebbero stati difficili da pensare e / o lenti a testare con test di integrazione.


+1 Trovo molto più facile costruire casi limite con test di simulazione piuttosto che popolare il database con quei casi.
Rob

Sono d'accordo con la maggior parte della tua risposta. Tuttavia, non sono sicuro di essere d'accordo sulla parte dell'adattatore. HTable è un dolore da prendere in giro perché è abbastanza bare metal. Ad esempio, se si desidera eseguire un'operazione get get in batch, è necessario creare un gruppo di oggetti Get, metterli in un elenco, quindi chiamare HTable.batch (). Da una prospettiva beffarda, questo è un problema serio, perché devi creare un Matcher personalizzato che esamina l'elenco di oggetti get che passi a HTable.batch () e quindi restituisce i risultati corretti per quell'elenco di oggetti get (). Un dolore SERIO.
sangfroid,

Suppongo di poter creare una classe wrapper piacevole e amichevole per HTable che si occupasse di tutte quelle pulizie, ma a quel punto ... mi sento come se stessi costruendo un framework attorno a HTable, e dovrebbe davvero essere il mio lavoro? Di solito "Costruiamo un framework!" è un segno sto andando nella direzione sbagliata. Potrei passare giorni a scrivere lezioni per rendere HBase più amichevole, e non so se sia un grande uso del mio tempo. Inoltre, sto mettendo a punto un'interfaccia o un wrapper anziché solo il semplice vecchio oggetto HTable, e questo renderebbe sicuramente il mio codice più complesso.
sangfroid,

Ma sono d'accordo sul tuo punto principale, che non si dovrebbe scrivere beffe per le classi che non possiedono. E sicuramente d'accordo che non ha senso scrivere un test simulato che testa la stessa cosa di un test di integrazione. Sembrerebbe che i mock siano i migliori per testare interfacce / contratti. Grazie per il consiglio: questo mi ha aiutato molto!
sangfroid,

Non ho idea di cosa faccia realmente HTable e di come lo usi, quindi non portare il mio esempio di wrapper alla lettera. Avevo menzionato un wrapper / adattatore perché pensavo che la cosa da avvolgere fosse relativamente piccola. Non è necessario introdurre una replica one-to-one in HTable, che ovviamente sarebbe una seccatura, per non parlare di un intero framework - ma è necessaria una cucitura , un'interfaccia tra il regno della propria applicazione e quello di HTable. Dovrebbe riformulare alcune delle funzionalità di HTable secondo i termini della propria applicazione. Il modello del repository è una perfetta incarnazione di tale cucitura quando si tratta di accesso ai dati.
guillaume31,

11

filosoficamente, i test che usano beffe dovrebbero avere la precedenza sui test che usano un endpoint live

Penso almeno che questo sia un punto delle attuali controversie in corso tra i sostenitori del TDD.

Il mio punto di vista personale va oltre quello di dire che un test basato su simulazioni è principalmente un modo di rappresentare una forma di contratto di interfaccia ; idealmente si rompe (cioè fallisce) se e solo se si cambia l'interfaccia . E come tale, in un linguaggio tipicamente fortemente tipizzato come Java, e quando si utilizza un'interfaccia definita in modo esplicito, è quasi del tutto superfluo: il compilatore ti avrà già detto se hai cambiato l'interfaccia.

L'eccezione principale è quando si utilizza un'interfaccia molto generica, forse basata su annotazioni o riflessioni, che il compilatore non è in grado di sorvegliare utilmente automaticamente. Anche in questo caso dovresti verificare se esiste un modo per eseguire la validazione a livello di codice (eq una libreria di controllo della sintassi SQL) piuttosto che a mano usando i mock.

È quest'ultimo caso che si sta eseguendo quando si esegue il test con un database locale "live"; l'implementazione di htable prende il via e applica una validazione molto più completa del contratto interfacve di quanto si possa mai pensare di scrivere a mano.

Sfortunatamente, un uso molto più comune dei test basati su simulazioni è il test che:

  • passa per qualunque codice fosse al momento della scrittura del test
  • non fornisce alcuna garanzia in merito a proprietà del codice diverse da quelle esistenti e dal tipo di esecuzione
  • fallisce ogni volta che cambi quel codice

Tali prove dovrebbero ovviamente essere cancellate a vista.


1
Non posso sostenerlo abbastanza. Preferirei avere una copertura di 1pc con ottimi test piuttosto che una copertura di filler di 100pc.
Ian,

3
I test basati su simulazioni descrivono effettivamente il contratto utilizzato da 2 oggetti per parlare insieme, ma vanno ben oltre ciò che il sistema di tipi di un linguaggio come Java può fare. Non si tratta solo di firme dei metodi, possono anche specificare intervalli di valori validi per argomenti o risultati restituiti, quali eccezioni sono accettabili, in quale ordine e quante volte i metodi possono essere chiamati, ecc. Il compilatore da solo non ti avviserà se lì sono cambiamenti in quelli. In tal senso, non credo che siano superflue. Vedi infoq.com/presentations/integration-tests-scam per maggiori informazioni sui test basati su simulazioni.
guillaume31,

1
... d'accordo cioè testare la logica attorno alla chiamata di interfaccia
Rob

1
Può certamente aggiungere eccezioni non controllate, precondizioni non dichiarate e stato implicito all'elenco di cose che rendono un'interfaccia meno tipicamente statica, e quindi giustificano test basati su simulazioni invece di semplici compilazioni. Il problema, tuttavia, è che quando questi aspetti fanno il cambiamento, la loro specifica è implicita e distribuito tra le prove di tutti i clienti. Che probabilmente non verranno aggiornati, e quindi siediti in silenzio nascondendo un bug dietro un segno di spunta verde.
soru,

"la loro specifica è implicita": no se si scrivono test di contratto per le proprie interfacce ( blog.thecodewhisperer.com/2011/07/07/contract-tests-an-example ) e si attenono ad essi quando si impostano i mock.
guillaume31,

5

Quanto tempo richiede l'esecuzione di un test basato su endpoint rispetto a un test simulato? Se è significativamente più lungo, quindi sì, vale la pena investire il tempo necessario per scrivere i test per rendere più veloci i test unitari, perché dovrai eseguirli molte, molte volte. Se non è significativamente più lungo, anche se i test basati sugli endpoint non sono test di unità "puri", purché facciano un buon lavoro di test dell'unità, non c'è motivo di essere religiosi al riguardo.


4

Sono completamente d'accordo con la risposta di guillaume31, mai finti tipi che non possiedi !.

Normalmente un dolore nel test (deridendo un'interfaccia complessa) riflette un problema nel tuo progetto. Forse hai bisogno di astrazione tra il tuo modello e il tuo codice di accesso ai dati, ad esempio utilizzando un'architettura esagonale e un modello di repository è il modo più comune per risolvere questo tipo di problemi.

Se si desidera eseguire un test di integrazione per verificare le cose, eseguire un test di integrazione, se si desidera eseguire un test di unità perché si sta testando la logica, eseguire un test di unità e isolare la persistenza. Ma fare un test di integrazione perché non sai come isolare la tua logica da un sistema esterno (o perché isolare è un dolore) è un grande odore, stai scegliendo l'integrazione rispetto all'unità per una limitazione nel tuo progetto non per un reale bisogno per testare l'integrazione.

Dai un'occhiata a questo discorso da Ian Cooper: http://vimeo.com/68375232 , parla di architettura esagonale e test, parla di quando e cosa deridere, un discorso davvero ispirato che risolve molte domande come la tua sul vero TDD .


1

TL; DR - Per come la vedo io, dipende da quanti sforzi finisci per fare i test e se invece sarebbe stato meglio spenderli di più sul tuo sistema reale.

Versione lunga:

Alcune buone risposte qui, ma la mia opinione è diversa: il testing è un'attività economica che deve ripagare da sola, e se il tempo che passi non viene restituito nello sviluppo e nell'affidabilità del sistema (o qualsiasi altra cosa che cerchi di uscire) di test) allora potresti fare un cattivo investimento; sei nel business della costruzione di sistemi, non di scrivere test. Pertanto, ridurre lo sforzo di scrivere e mantenere i test è cruciale.

Ad esempio, alcuni dei valori principali che ottengo dai test sono:

  • Affidabilità (e quindi velocità di sviluppo): refactor code / integra un nuovo framework / scambia un componente / porta su una piattaforma diversa, sii sicuro che le cose funzionino ancora
  • Feedback di progettazione: il classico feedback TDD / BDD "usa il tuo codice" sulle tue interfacce di livello medio / basso

I test su un endpoint attivo dovrebbero comunque fornire questi.

Alcuni svantaggi per i test su un endpoint live:

  • Impostazione dell'ambiente: la configurazione e la standardizzazione dell'ambiente in esecuzione dei test richiede più lavoro e configurazioni dell'ambiente leggermente diverse potrebbero comportare comportamenti leggermente diversi
  • Apolidia: lavorare contro un endpoint in tempo reale può finire per promuovere la scrittura di test che si basano su uno stato di endpoint mutante, che è fragile e su cui è difficile ragionare (cioè quando qualcosa fallisce, sta fallendo a causa di uno stato strano?)
  • L'ambiente di esecuzione del test è fragile: se un test ha esito negativo, è il test, il codice o l'endpoint live?
  • Velocità di marcia: un endpoint live è generalmente più lento e talvolta è più difficile parallelizzare
  • Creare casi limite per i test - di solito banali con un finto, a volte un dolore con un endpoint attivo (ad esempio quelli difficili da configurare sono errori di trasporto / HTTP)

Se mi trovassi in questa situazione e gli inconvenienti non sembrassero essere un problema, mentre deridere l'endpoint rallentava notevolmente la mia scrittura di test, testerei contro un endpoint dal vivo in un battito cardiaco, purché sarei sicuro di ricontrollare dopo un po 'per vedere che gli svantaggi non si rivelano un problema in pratica.


1

Dal punto di vista del test ci sono alcuni requisiti che sono assolutamente necessari:

  • I test (unità o altro) non devono mai avere un modo per toccare i dati di produzione
  • I risultati di un test non devono mai influenzare i risultati di un altro test
  • Devi sempre partire da una posizione nota

Questa è una grande sfida quando ci si connette a qualsiasi fonte che mantiene lo stato al di fuori dei test. Non è "puro" TDD, ma l'equipaggio di Ruby on Rails ha risolto questo problema in un modo che poteva essere adattato ai tuoi scopi. Il framework di test delle rotaie ha funzionato in questo modo:

  • La configurazione del test è stata selezionata automaticamente durante l'esecuzione dei test unitari
  • Il database è stato creato e inizializzato all'inizio dell'esecuzione di unit test
  • Il database è stato eliminato dopo l'esecuzione dei test unitari
  • Se si utilizza SqlLite, la configurazione del test ha utilizzato un database RAM

Tutto questo lavoro è stato integrato nel cablaggio di test e funziona abbastanza bene. C'è molto di più, ma le basi sono sufficienti per questa conversazione.

Sui diversi team con cui ho lavorato nel tempo, faremmo delle scelte che promuovono il test del codice anche se non fosse il percorso più corretto. Idealmente, dovremmo racchiudere tutte le chiamate in un archivio dati con il codice che abbiamo controllato. In teoria, se qualcuno di questi vecchi progetti ottenesse nuovi finanziamenti, potremmo tornare indietro e spostarli dall'essere vincolati al database da Hadoop concentrando la nostra attenzione solo su una manciata di classi.

Gli aspetti importanti non sono quello di confondere con i dati di produzione e assicurarsi che si stia davvero testando ciò che si ritiene di essere testato. È davvero importante essere in grado di ripristinare il servizio esterno su una base nota su richiesta, anche dal tuo codice.

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.