Unit test di una classe che utilizza DI senza test su internals

12

Ho una classe che è refactored in 1 classe principale e in 2 classi più piccole. Le classi principali usano il database (come fanno molte mie classi) e inviano un'email. Quindi la classe principale ha una IPersonRepository e una IEmailRepository iniettata che a sua volta invia alle 2 classi più piccole.

Ora voglio testare l'unità principale della classe principale, e ho imparato a non smantellare il funzionamento interno della classe, perché dovremmo essere in grado di cambiare il funzionamento interno senza interrompere i test unitari.

Tuttavia, poiché la classe utilizza IPersonRepository e IEmailRepository , I HAVE specifica i risultati (mock / dummy) per alcuni metodi per IPersonRepository . La classe principale calcola alcuni dati in base ai dati esistenti e li restituisce. Se voglio testarlo, non vedo come posso scrivere un test senza specificare che IPersonRepository.GetSavingsByCustomerId restituisce x. Ma poi il mio test unitario "conosce" il funzionamento interno, perché "conosce" i metodi per deridere e quali no.

Come posso testare una classe che ha inserito le dipendenze, senza che il test sia a conoscenza degli interni?

sfondo:

Nella mia esperienza molti test come questo creano dei mock per i repository e poi forniscono i dati giusti per i mock o testano se un metodo specifico è stato chiamato durante l'esecuzione. Ad ogni modo, il test conosce gli interni.

Ora ho visto una presentazione sulla teoria (che ho sentito prima) che il test non dovrebbe conoscere l'implementazione. Innanzitutto perché non stai testando come funziona , ma anche perché quando cambi l'implementazione tutti i test delle unità falliscono perché "sanno" dell'implementazione. Sebbene mi piaccia l'idea che i test non siano a conoscenza dell'implementazione, non so come realizzarlo.

    
posta Michel 05.09.2016 - 16:21
fonte

3 risposte

7

The main class calculates some data based on existing data and returns that. If I want to test that, I don't see how I can write a test without specifying that the IPersonRepository.GetSavingsByCustomerId returns x. But then my unit test 'knows' about the internal workings, because it 'knows' which methods to mock and which not.

Hai ragione che si tratta di una violazione del principio "non testare gli interni" ed è comune a tutti i cittadini.

How can I test a class which has injected dependencies, without the test knowing about the internals?

Esistono due soluzioni che puoi adottare per ovviare a questa violazione:

1) Fornisci una simulazione completa di IPersonRepository . Il tuo approccio attualmente descritto è quello di accoppiare il mocking al funzionamento interno del metodo in esame, prendendo in giro solo i metodi che chiamerà. Se fornisci mock per tutti i metodi di IPersonRepository , allora rimuovi quell'accoppiamento. I meccanismi interni possono cambiare senza influenzare la simulazione, rendendo il test meno fragile.

Questo approccio ha il vantaggio di mantenere semplice il meccanismo DI, ma può creare molto lavoro se l'interfaccia definisce molti metodi.

2) Non iniettare IPersonRepository , iniettare il metodo GetSavingsByCustomerId o iniettare un valore di risparmio. Il problema con l'iniezione di intere implementazioni dell'interfaccia è che si sta iniettando ("tell, do ask") un sistema "ask, do not tell", mescolando i due approcci. Se si utilizza un approccio di "pura DI", il metodo deve essere fornito (detto) al metodo esatto da chiamare se desidera un valore di risparmio, piuttosto che ricevere un oggetto (che deve quindi chiedere effettivamente il metodo da chiamare).

Il vantaggio di questo approccio è che evita la necessità di mock (oltre ai metodi di test che si iniettano nel metodo sotto test). Lo svantaggio è che può far cambiare le firme dei metodi in risposta ai requisiti della modifica del metodo.

Entrambi gli approcci hanno i loro pro e contro, quindi scegli quello più adatto alle tue esigenze.

    
risposta data 05.09.2016 - 17:03
fonte
1

Il mio approccio è creare versioni "fittizie" dei repository che leggono da semplici file che contengono i dati necessari.

Questo significa che il test individuale non "conosce" l'impostazione della simulazione, anche se ovviamente il progetto di test generale farà riferimento alla simulazione e avrà i file di installazione ecc.

Questo evita la complessa configurazione di oggetti fittizi richiesta dai framework di simulazione e consente di utilizzare gli oggetti "simulati" nelle istanze reali dell'applicazione per i test dell'interfaccia utente e simili.

Poiché il 'mock' è completamente implementato, piuttosto che l'impostazione specifica per lo scenario di test, un cambio di implementazione, per esempio, lascia che GetSavingsForCustomer ora cancelli anche il cliente. Non interromperà i test (a meno che, naturalmente, non interrompa davvero i test), dovrai solo aggiornare la tua implementazione di simulazione singola e tutti i test verranno eseguiti su di essa senza cambiare la loro configurazione

    
risposta data 05.09.2016 - 16:32
fonte
1

I test unitari sono solitamente test whitebox (hai accesso al codice reale). Pertanto è giusto conoscere gli interni in una certa misura, tuttavia per i principianti è più facile non farlo, perché non si dovrebbe testare il comportamento interno (come "chiamare prima un metodo, poi b e poi ancora ").

L'iniezione di una simulazione che fornisce dati è ok, poiché la classe (unità) dipende da quei dati esterni (o dal fornitore di dati). Tuttavia, dovresti verificare i risultati (non il modo di raggiungerli)! per esempio. fornisci un'istanza Person e verifica che un'email sia stata inviata all'indirizzo email corretto, ad es. fornendo anche un mock per l'e-mail, che il mock non fa altro che memorizzare l'indirizzo e-mail del destinatario per un successivo accesso tramite il codice di test. (Penso che Martin Fowler li chiami Stubs piuttosto che Mocks, comunque)

    
risposta data 05.09.2016 - 17:16
fonte

Leggi altre domande sui tag