Da una prospettiva TDD, sono una persona cattiva se provo contro un endpoint live invece di un mock?

16

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

Lavoro molto con HBase , e l'interfaccia client principale, HTable, è un vero e proprio dolore da prendere in giro. Mi bastano 3 o 4 volte più a lungo per scrivere i miei test unitari di quanto non faccia per scrivere test che utilizzano un endpoint live.

So che, filosoficamente, i test che usano i mock dovrebbero avere la priorità sui test che usano un endpoint live. Ma il mocking HTable è un dolore serio, e non lo sono davvero certo offre molto vantaggio rispetto ai test su un'istanza HBase live.

Tutti i membri del mio team eseguono un'istanza HBase single-node sulla loro workstation e abbiamo istanze HBase a nodo singolo in esecuzione sulle nostre scatole Jenkins, quindi non è un problema di disponibilità. I test degli endpoint live impiegano ovviamente più tempo per essere eseguiti rispetto ai test che usano i mock, ma non ci interessa molto.

In questo momento, scrivo test di endpoint in tempo reale e test basati su mock per tutte le mie classi. Mi piacerebbe mollare i mock, ma non voglio che la qualità diminuisca di conseguenza.

Che cosa ne pensi?

    
posta sangfroid 18.03.2014 - 21:15
fonte

6 risposte

23
  • La mia prima raccomandazione sarebbe quella di non falsificare i tipi che non possedere . Hai detto che HTable è un vero rompiscatole - forse dovresti avvolgerlo invece in un adattatore che espone il 20% delle funzionalità di HTable di cui hai bisogno, e prendi in giro il wrapper dove necessario.

  • Detto questo, supponiamo che stiamo parlando di tipi che possiedi. Se i tuoi test di simulazione sono incentrati su scenari di percorso felici in cui tutto procede senza intoppi, non perderai nulla eliminandoli perché i tuoi test di integrazione probabilmente stanno già testando esattamente gli stessi percorsi.

    Tuttavia, i test isolati diventano interessanti quando inizi a pensare a come il tuo sistema in prova dovrebbe reagire a ogni piccola cosa che potrebbe accadere come definito nel contratto del suo collaboratore, indipendentemente dall'oggetto concreto 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 rimangono veloci e gestibili.

    Per essere più concreti, cosa succede se uno dei metodi della tua scheda HTable restituisce una lista vuota? Cosa succede se restituisce nulla? Cosa succede se genera un'eccezione di connessione? Dovrebbe essere definito nel contratto dell'adapter se una di queste cose potrebbe accadere, e uno qualsiasi dei suoi consumatori dovrebbe essere preparato ad affrontare queste situazioni , quindi la necessità di test per loro.

Per riassumere: non vedrai alcun calo di qualità rimuovendo i test basati su simulazione se hanno testato esattamente le stesse cose dei tuoi test di integrazione . Tuttavia, provare a immaginare ulteriori test isolati (e test contrattuali ) può aiutare pensi estesamente alle tue interfacce / contratti e aumenta la qualità affrontando difetti che sarebbe stato difficile da pensare e / o lenti da testare con i test di integrazione.

    
risposta data 19.03.2014 - 17:59
fonte
11

philosophically, tests that use mocks should take priority over tests that use a live endpoint

Penso che sia un punto attuale < a href="http://martinfowler.com/articles/mocksArentStubs.html"> controversie in corso tra i sostenitori del TDD.

Il mio punto di vista personale va oltre quello di dire che un test di simulazione è principalmente un modo di rappresentare una forma di contratto di interfaccia ; idealmente si rompe (non riesce) se e solo se cambi l'interfaccia . E come tale, in linguaggi tipicamente strongmente tipizzati come Java, e quando si utilizza un'interfaccia definita esplicitamente, è 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 eseguire automaticamente la polizia in modo utile. Anche in questo caso dovresti controllare se c'è un modo di fare validazione programmatica (e.q. una libreria di controllo della sintassi SQL) piuttosto che a mano usando i mock.

È quest'ultimo caso che stai facendo quando testi con un database locale "live"; l'implementazione di htable entra in gioco e applica una convalida del contratto interfacciale molto più completa di quanto si possa pensare di scrivere a mano.

Sfortunatamente, un uso molto più comune dei test di simulazione è il test che:

  • passa per qualunque sia il codice al momento della stesura del test
  • non fornisce garanzie su alcuna proprietà del codice diversa da quella esistente e il tipo di esecuzioni
  • fallisce ogni volta che modifichi quel codice

Tali test dovrebbero ovviamente essere cancellati a vista.

    
risposta data 19.03.2014 - 00:02
fonte
5

Quanto tempo impiega un test basato su endpoint per eseguire un test basato su simulazione? Se è molto più lungo, allora sì, vale la pena investire il tempo di scrittura del test per rendere più veloci i test unitari, perché dovrai eseguirli molte, molte volte. Se non è significativamente più lungo, anche se i test basati sull'endpoint non sono test di unità "puri", a patto che stiano facendo un buon lavoro di test dell'unità, non c'è motivo di essere religiosi.

    
risposta data 18.03.2014 - 21:42
fonte
4

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

Normalmente un dolore nel test (prendendo in giro un'interfaccia complessa) riflette un problema nel tuo progetto. Forse hai bisogno di qualche astrazione tra il tuo modello e il tuo codice di accesso ai dati, l'esempio di modulo usando un'architettura esagonale e un modello di repository è il modo più usuale per risolvere questo tipo di problemi.

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

Dai un'occhiata a questo modulo di discussione Ian cooper: link , parla di architettura esagonale e test, parla di quando e cosa finta, un discorso davvero ispirato che risolve molte domande come la tua sul TDD reale.

    
risposta data 20.03.2014 - 10:05
fonte
1

TL; DR - Il modo in cui lo vedo dipende da quanta fatica si finisce a spendere per i test e se sarebbe stato meglio spenderne di più sul proprio sistema attuale .

Versione lunga:

Alcune buone risposte qui, ma la mia opinione è diversa: il test è un'attività economica che deve ripagare per sé e se il tempo che si spende non viene restituito nello sviluppo e nell'affidabilità del sistema (o qualsiasi altra cosa si guarda per uscire dai test) allora potresti fare un cattivo investimento; sei nel business di costruire sistemi, non scrivere test. Pertanto, ridurre lo sforzo di scrivere e mantenere i test è fondamentale.

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

  • Affidabilità (e quindi velocità di sviluppo): codice refactoring / integrare un nuovo framework / scambiare un componente / porta su una piattaforma diversa, essere sicuri che la roba funzioni ancora
  • Feedback sul design: il classico TDD / BDD "usa il tuo codice" feedback sulle tue interfacce di basso / medio livello

I test contro un endpoint live dovrebbero comunque fornire questi

Alcuni svantaggi per il test su un endpoint live:

  • Configurazione dell'ambiente: la configurazione e la standardizzazione dell'ambiente di test in esecuzione sono più efficaci e configurazioni dell'ambiente leggermente diverse potrebbero comportare un comportamento leggermente diverso
  • L'apolidia - lavorare contro un endpoint live può finire per promuovere test di scrittura che si basano su uno stato dell'endpoint mutante, che è fragile e difficile da ragionare (cioè quando qualcosa non funziona, è in errore a causa di uno stato strano?)
  • Il test dell'ambiente di test è fragile - se un test fallisce, è il test, il codice o l'endpoint live?
  • Velocità di esecuzione: un endpoint live di solito è più lento e talvolta è più difficile parallelizzare
  • Creazione di casi limite per il test - solitamente banali con una simulazione, a volte un dolore con un endpoint live (ad esempio, quelli difficili da impostare sono errori di trasporto / HTTP)

Se fossi in questa situazione, e gli svantaggi non sembravano essere un problema mentre il beffardo endpoint rallentava notevolmente la mia scrittura scritta, testavo contro un endpoint live in un battito cardiaco , a patto di essere sicuro di ricontrollare dopo un po 'per vedere che gli inconvenienti non si rivelano in pratica un problema.

    
risposta data 24.03.2014 - 20:59
fonte
1

Da una prospettiva di test ci sono alcuni requisiti assolutamente indispensabili:

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

È una grande sfida quando ci si connette a qualsiasi fonte che mantiene lo stato al di fuori dei propri test. Non è TDD "puro", ma l'equipaggio di Ruby on Rails ha risolto questo problema in un modo che potrebbe 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 delle unità
  • Il database è stato creato e inizializzato all'inizio dei test delle unità in esecuzione
  • Il database è stato cancellato dopo l'esecuzione dei test unitari
  • Se si utilizza SqlLite, la configurazione di test ha utilizzato un database RAM

Tutto questo lavoro è stato integrato nell'imbracatura 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 corso del tempo, avremmo fatto delle scelte che avrebbero promuovere il codice sottoposto a test anche se non era il percorso più corretto. Idealmente, dovremmo avvolgere tutte le chiamate verso un archivio dati con il codice che abbiamo controllato. In teoria, se qualcuno di questi vecchi progetti avesse nuovi finanziamenti potremmo tornare indietro e spostarli dal database legato a Hadoop rilanciando la nostra attenzione solo su una manciata di classi.

Gli aspetti importanti non consistono nel compromettere i dati di produzione e assicurarti di testare veramente ciò che pensi di testare. È molto importante poter reimpostare il servizio esterno su una base nota su richiesta, anche dal tuo codice.

    
risposta data 24.03.2014 - 21:59
fonte

Leggi altre domande sui tag