Le mazze violano il principio Aperto / Chiuso?

13

Qualche tempo fa ho letto, in 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 l'implementazione di un metodo è cambiata, non è necessario modificare il caso di test, in quanto ciò interromperebbe il contratto che garantisce il funzionamento del sistema in prova. In altre parole, un test dovrebbe fallire se il metodo non funziona, ma non perché l'implementazione sia cambiata.

Questo ha richiamato la mia attenzione quando parliamo di derisione. Dal momento che il mocking si affida pesantemente alle chiamate di aspettativa dalle dipendenze del sistema sotto test, i mock sono strettamente accoppiati con l'implementazione piuttosto che con l'interfaccia.

Durante la ricerca di simulacro vs stub , diversi articoli concordano che gli stub dovrebbero essere usati al posto dei mock, poiché non dipendono dalle aspettative delle dipendenze, il che significa che il test non ha bisogno di conoscenza del sistema sottostante implementazione del test.

Le mie domande sarebbero:

  1. Le motte violano il principio di apertura / chiusura?
  2. C'è qualcosa che manca nell'argomento a favore degli stub nell'ultimo paragrafo, che rendono gli stub non così grandi contro i mock?
  3. Se sì, quando sarebbe opportuno utilizzare un caso di simulazione e quando sarebbe opportuno utilizzare gli stub?
posta Christopher Francisco 12.08.2015 - 23:28
fonte

4 risposte

4
  1. Non vedo perché i mock violino il principio aperto / chiuso. Se potresti spiegarci perché pensi che potrebbero, allora potremmo essere in grado di alleviare i tuoi dubbi.

  2. L'unico svantaggio di stub che posso pensare è che in genere richiedono più lavoro da scrivere di mock, dal momento che ognuno di essi è in realtà un'implementazione alternativa di un'interfaccia dipendente, quindi generalmente deve fornire un implementazione completa (o in modo convincente) dell'interfaccia dipendente. Per darvi un esempio estremo, se il vostro sottosistema sottoposto a test richiama un RDBMS, una simulazione dell'RDBMS risponderebbe semplicemente a query specifiche che il sottosistema ha rilasciato, ottenendo serie predeterminate di dati di test. D'altro canto, un'implementazione alternativa sarebbe un RDBMS in memoria completo, possibilmente con l'onere aggiuntivo di dover emulare i quirk del reale RDBMS client-server che si sta utilizzando in produzione. (Fortunatamente, abbiamo cose come HSQLDB, quindi possiamo effettivamente farlo, ma comunque è un po 'complicato, soprattutto perché si attacca principalmente allo standard SQL e non si preoccupa di emulare le stranezze di nessuno.)

  3. I buoni casi d'uso per il mocking sono quando l'interfaccia dipendente è troppo complicata per scrivere un'implementazione alternativa per esso, o se sei sicuro di voler scrivere una volta la falsa frase e non toccarla più. In questi casi, vai avanti e usa una finta e sporca finta. 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 sottoposto a test, scegli sicuramente un'implementazione alternativa che sia piacevole e pulita, e richieda la manutenzione solo nel caso in cui l'interfaccia cambi, invece di richiedere manutenzione ogni volta che l'interfaccia modifica e ogni volta che l'implementazione del sottosistema viene sottoposta a test.

P.S. La persona a cui ti riferisci potrebbe essere stata io, in una delle mie altre risposte relative ai test qui su programmers.stackexchange.com, ad esempio questo .

    
risposta data 13.08.2015 - 03:54
fonte
9
  1. Il principio Aperto / Chiuso riguarda principalmente la possibilità di modificare il comportamento di una classe senza modificarla. Pertanto, l'iniezione di una dipendenza di componente fittizia in una classe sottoposta a test non la viola.

  2. Il problema con i test double (mock / stub) è che fondamentalmente si fanno ipotesi arbitrarie su come la classe in prova interagisce con il proprio ambiente. Se tali aspettative sono sbagliate, è probabile che si presentino dei problemi una volta implementato il codice. Se puoi permetterti, prova il tuo codice all'interno degli stessi vincoli rispetto a quello che limita il tuo ambiente di produzione. Se non è possibile, rendere possibili le ipotesi minime e simulare / stub solo le periferiche del sistema (database, servizio di autenticazione, client HTTP, ecc.).

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

    
risposta data 12.08.2015 - 23:55
fonte
6

Nota: sto assumendo che tu stia definendo Mock come "una classe senza implementazione, solo qualcosa che puoi monitorare" e che Stub sia "parzialità parziale, ovvero usa alcuni dei reali comportamenti della classe implementata", come per questa domanda di overflow dello stack .

Non sono sicuro del motivo per cui pensi che il consenso sia l'uso di stub, ad esempio è esattamente l'opposto in Documentazione di Mockito

As usual you are going to read the partial mock warning: Object oriented programming is more less tackling complexity by dividing the complexity into separate, specific, SRPy objects. How does partial mock fit into this paradigm? Well, it just doesn't... Partial mock usually means that the complexity has been moved to a different method on the same object. In most cases, this is not the way you want to design your application.

However, there are rare cases when partial mocks come handy: dealing with code you cannot change easily (3rd party interfaces, interim refactoring of legacy code etc.) However, I wouldn't use partial mocks for new, test-driven & well-designed code.

Questa documentazione lo dice meglio di me. L'utilizzo di mock ti consente di testare solo una classe particolare e nient'altro; se hai bisogno di parate 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 sopportare un refactoring. Le mosse non violano il principio di open-closed, perché sono sempre e comunque utilizzate nei test, non sono veri e propri cambiamenti a quel codice. Di solito vengono comunque generati al volo da una libreria come cglib.

    
risposta data 12.08.2015 - 23:45
fonte
2

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

È facile vedere che l'unico test che dovrebbe avere importanza è quello che verifica l'interfaccia. Tuttavia, in realtà, è spesso più efficace testare tale interfaccia testando i meccanismi interni.

Ad esempio, è quasi impossibile testare qualsiasi requisito negativo, ad esempio "l'implementazione non genera eccezioni". Prendi in considerazione un'interfaccia della mappa implementata con una hashmap. Vuoi essere certo che l'hashmap soddisfi l'interfaccia della mappa, senza lanci, anche quando deve rimettere a posto le cose (il che potrebbe diventare rischioso). È possibile testare ogni combinazione di input per assicurarsi che soddisfino i requisiti dell'interfaccia, ma ciò potrebbe richiedere più tempo rispetto alla morte termica dell'universo. Invece, rompi un po 'l'incapsulamento e sviluppi mock che interagiscono più strettamente, costringendo l'hashmap a fare esattamente il rehash necessario a garantire che l'algoritmo di rilocalizzazione non venga lanciato.

Tl / Dr: farlo "dal libro" è bello, ma quando arriva il momento critico, avere un prodotto sulla scrivania del tuo capo entro venerdì è più utile di una suite di test "da libro" che richiede fino al calore morte dell'universo per confermare la conformità.

    
risposta data 13.08.2015 - 03:51
fonte

Leggi altre domande sui tag