Le simulazioni violano il principio aperto / chiuso?


13

Qualche tempo fa ho letto, su una risposta Stack Overflow che non riesco a trovare, una frase che spiegava che dovresti testare le API pubbliche e l'autore ha detto che dovresti testare le interfacce. L'autore ha anche spiegato che se un'implementazione del metodo è cambiata, non è necessario modificare il caso di test, in quanto ciò infrange il contratto per assicurarsi che il sistema in prova funzioni. In altre parole, un test dovrebbe fallire se il metodo non funziona, ma non perché l'implementazione è cambiata.

Questo ha attirato la mia attenzione quando parliamo di derisione. Dal momento che la derisione si basa fortemente sulle chiamate di aspettativa dal sistema sotto le dipendenze del test, le beffe sono strettamente associate all'implementazione piuttosto che all'interfaccia.

Durante la ricerca di mock vs stub , diversi articoli concordano sul fatto che gli stub dovrebbero essere usati al posto dei mock, poiché non si basano sulle aspettative delle dipendenze, il che significa che il test non necessita di conoscenza del sistema sottostante in fase di implementazione del test.

Le mie domande sarebbero:

  1. Le beffe violano il principio aperto / chiuso?
  2. C'è qualcosa che manca nell'argomento a favore degli stub nell'ultimo paragrafo, che rende gli stub non così fantastici contro le beffe?
  3. Se è così, quando sarebbe un buon caso da deridere e quando sarebbe un buon caso da usare con gli stub?


8
Since mocking relays heavily on expectation calls from system under test's dependencies...Penso che sia qui che stai andando storto. Una finta è una rappresentazione artificiale di un sistema esterno. Non rappresenta in alcun modo il sistema esterno, tranne nella misura in cui simula il sistema esterno in modo tale da consentire l'esecuzione di test su codice avente dipendenze da detto sistema esterno. Avrai comunque bisogno di test di integrazione per dimostrare che il tuo codice funziona con il sistema reale e non modificato.
Robert Harvey,

8
Per dirla in altro modo, la simulazione è un'implementazione sostitutiva. Questo è il motivo per cui abbiamo programmato un'interfaccia in primo luogo, in modo da poter usare le beffe come supporto per l'implementazione reale. In altre parole, le beffe sono disaccoppiate dall'attuazione effettiva , non accoppiate.
Robert Harvey,

3
"In altre parole, un test dovrebbe fallire se il metodo non funziona, ma non perché l'implementazione è cambiata", questo non è sempre vero. Esistono molte circostanze in cui è necessario modificare sia l'implementazione che i test.
whatsisname

Risposte:


4
  1. Non vedo perché le beffe violerebbero il principio aperto / chiuso. Se puoi spiegarci il motivo per cui pensi che potrebbero farlo, allora potremmo essere in grado di alleviare le tue preoccupazioni.

  2. L'unico svantaggio degli stub a cui riesco a pensare è che in genere richiedono più lavoro per scrivere rispetto ai mock, poiché ognuno di essi è in realtà un'implementazione alternativa di un'interfaccia dipendente, quindi in genere deve fornire un completo (o in modo convincente completo) implementazione dell'interfaccia dipendente. Per darvi un esempio estremo, se il vostro sottosistema sottoposto a test invoca un RDBMS, allora un finto RDBMS risponderebbe semplicemente a specifiche query note per essere emesse dal sottosistema sottoposto a test, producendo serie predeterminate di dati di test. D'altra parte, un'implementazione alternativa sarebbe un RDBMS in-memory in piena regola, possibilmente con l'onere aggiuntivo di dover emulare le stranezze del RDBMS client-server reale che si sta utilizzando in produzione. (Fortunatamente, abbiamo cose come HSQLDB, quindi possiamo effettivamente farlo, ma comunque,

  3. I buoni casi d'uso per il derisione sono quando l'interfaccia dipendente è troppo complicata per scrivere un'implementazione alternativa per essa, o se sei sicuro che scriverai il mock una sola volta e non lo toccherai mai più. In questi casi, vai avanti e usa un finto veloce e sporco. Di conseguenza, i buoni casi d'uso per gli stub (implementazioni alternative) sono praticamente tutto il resto. Soprattutto se prevedi di impegnarti in una relazione a lungo termine con il sottosistema sotto test, scegli sicuramente un'implementazione alternativa che sarà piacevole e pulita e richiederà manutenzione solo nel caso in cui l'interfaccia cambi, invece di richiedere manutenzione ogni volta che l'interfaccia cambia e ogni volta che cambia l'implementazione del sottosistema in prova.

PS La persona a cui ti riferisci avrei potuto essere io, in una delle mie altre risposte relative ai test qui su programmers.stackexchange.com, ad esempio questa .


an alternative implementation would be a full-blown in-memory RDBMS- Non devi necessariamente andare così lontano con uno stub.
Robert Harvey,

@RobertHarvey bene, con HSQLDB e H2 non è così difficile andare davvero così lontano. Probabilmente è più difficile fare qualcosa per metà per non andare così lontano. Ma se decidi di farlo da solo, dovrai iniziare scrivendo un parser SQL. Certo, puoi tagliare alcuni angoli, ma c'è molto lavoro . Comunque, come ho detto sopra, questo è solo un esempio estremo.
Mike Nakis,

9
  1. Il principio Open / Closed riguarda principalmente la possibilità di cambiare il comportamento di una classe senza modificarlo. Pertanto, l'iniezione di una dipendenza del componente beffardo all'interno di una classe sotto test non la viola.

  2. Il problema con i test double (mock / stub) è che in pratica si fanno ipotesi arbitrarie su come la classe sotto test interagisce con il suo ambiente. Se tali aspettative sono sbagliate, è probabile che si verifichino alcuni problemi una volta distribuito il codice. Se te lo puoi permettere, testa il tuo codice con gli stessi vincoli di quello che circonda il tuo ambiente di produzione. Se non ci riesci, rendi possibili le ipotesi minime e simula / stub solo le periferiche del tuo sistema (database, servizio di autenticazione, client HTTP, ecc ...).

L'unico motivo valido per cui, IMHO, un doppio dovrebbe essere usato, è quando è necessario registrare le sue interazioni con la classe sottoposta a test o quando è necessario fornire dati falsi (cosa che entrambe le tecniche possono fare). Fai attenzione, tuttavia, l'abuso di esso riflette una cattiva progettazione o un test che si basa troppo sull'API in fase di implementazione del test.


6

Nota: suppongo che tu stia definendo Mock come "una classe senza implementazione, solo qualcosa che puoi monitorare" e Stub come "parziale mock, ovvero usa alcuni dei comportamenti reali della classe implementata", come da questo Stack Domanda di overflow .

Non sono sicuro del motivo per cui pensi che il consenso sia l'uso di stub, ad esempio è esattamente il contrario nella documentazione di Mockito

Come al solito leggerai l'avviso di simulazione parziale: la programmazione orientata agli oggetti è più meno adatta alla complessità dividendola in oggetti separati, specifici, SRPy. Come si inserisce la derisione parziale in questo paradigma? Bene, semplicemente non ... La derisione parziale di solito significa che la complessità è stata spostata in un metodo diverso sullo stesso oggetto. Nella maggior parte dei casi, questo non è il modo in cui si desidera progettare l'applicazione.

Tuttavia, ci sono rari casi in cui le simulazioni parziali sono utili: gestire il codice che non è possibile modificare facilmente (interfacce di terze parti, refactoring provvisorio di codice legacy ecc.) Tuttavia, non utilizzerei le simulazioni parziali per nuove, guidate dai test e ben- codice progettato.

Quella documentazione lo dice meglio di me. L'uso di derisioni ti consente di testare solo quella particolare classe e nient'altro; se hai bisogno di derisioni parziali per ottenere il comportamento che stai cercando, probabilmente hai fatto qualcosa di sbagliato, stai violando SRP e così via e il tuo codice potrebbe reggere il confronto. Le simulazioni non violano il principio aperto-chiuso, poiché sono sempre utilizzate solo nei test, non sono vere modifiche a quel codice. Di solito sono generati al volo comunque da una libreria come cglib.


2
Dalla stessa domanda SO fornita (risposta accettata), questa è anche la definizione Mock / Stub a cui mi riferivo: gli oggetti Mock vengono utilizzati per definire le aspettative, ovvero: in questo scenario mi aspetto che il metodo A () venga chiamato con tali e tali parametri. Le beffe registrano e verificano tali aspettative. Gli stub, invece, hanno uno scopo diverso: non registrano o verificano le aspettative, ma piuttosto ci permettono di "sostituire" il comportamento, lo stato dell'oggetto "falso" al fine di utilizzare uno scenario di test ...
Christopher Francisco

2

Penso che il problema possa derivare dal presupposto che gli unici test validi siano quelli che soddisfano il test aperto / chiuso.

È facile vedere che l'unico test che dovrebbe importare è quello che verifica l'interfaccia. Tuttavia, in realtà, è spesso più efficace testare tale interfaccia testando il funzionamento interno.

Ad esempio, è quasi impossibile testare qualsiasi requisito negativo, come "l'implementazione non deve generare eccezioni". Prendi in considerazione un'interfaccia della mappa implementata con una hashmap. Vuoi essere certo che l'hashmap soddisfi l'interfaccia della mappa, senza lanciare, anche quando deve ridisegnare le cose (che potrebbero diventare rischiose). È possibile testare ogni combinazione di input per assicurarsi che soddisfino i requisiti dell'interfaccia, ma ciò potrebbe richiedere più tempo della morte termica dell'universo. Invece, rompi un po 'l'incapsulamento e sviluppi simulazioni che interagiscono più strettamente, costringendo la hashmap a fare esattamente la revisione necessaria per garantire che l'algoritmo di rehashing non venga lanciato.

Tl / Dr: farlo "dal libro" è bello, ma quando arriva la spinta, avere un prodotto sulla scrivania del tuo capo entro venerdì è più utile di una suite di test by-the-book che richiede fino alla morte del caldo del universo per confermare la conformità.

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.