TDD con pattern di repository


10

Nel mio nuovo progetto, ho deciso di provare con TDD. E all'inizio ho riscontrato un problema. La prima cosa che voglio fare nella mia applicazione è dare la possibilità di leggere i dati dall'origine dati. A tale scopo, desidero utilizzare il modello di repository. E adesso:

  • Se i test sono per l'implementazione reale dell'interfaccia del repository, testerò la classe che ha accesso al database e so che dovrei evitarlo.
  • Se i test sono per l'implementazione non reale del modello di repository, proverò bene ... solo finto. Non ci sarà alcun pezzo di codice di produzione testato in quei test unitari.

Ci sto pensando da due giorni e non riesco ancora a trovare una soluzione ragionevole. Cosa dovrei fare?

Risposte:


11

Quello che fa un repository è tradurre dal tuo dominio nel tuo framework DAL, come NHibernate o Doctrine, o le tue classi di esecuzione SQL. Ciò significa che il tuo repository chiamerà metodi su detto framework per svolgere i suoi compiti: il tuo repository costruisce le query necessarie per recuperare i dati. Se non si utilizza un framework ORM (spero che tu sia ...), il repository sarebbe il luogo in cui vengono create le istruzioni SQL non elaborate.

Il più semplice di questi metodi è il salvataggio: nella maggior parte dei casi questo passerà semplicemente l'oggetto dal repository all'unità di lavoro (o alla sessione).

public void Save(Car car)
{
    session.Save(car);
}

Ma diamo un'occhiata a un altro esempio, ad esempio recuperare un'auto dal suo ID. Potrebbe sembrare

public function GetCarWithId(String id)
{
    return Session.QueryOver<Car>()
                    .Where(x => x.Id == id)
                    .SingleOrDefault();
}

Ancora non troppo complesso, ma puoi immaginarlo a più condizioni (procurami tutte le macchine prodotte dopo il 2010 per tutti i marchi del gruppo "Volkswagen"), questo diventa complicato. Quindi, in vero stile TDD, devi testarlo. Esistono diversi modi per farlo.

Opzione 1: deride le chiamate effettuate al framework ORM

Certo, puoi deridere l'oggetto Session e semplicemente affermare che vengono fatte le chiamate giuste. Mentre questo testa il repository, non è realmente test- driven perché stai solo testando che il repository internamente abbia l'aspetto che desideri. Il test in pratica dice "il codice dovrebbe apparire così". Tuttavia, è un approccio valido ma sembra che questo tipo di test abbia un valore molto scarso.

Opzione 2: (ri) compilare il database dai test

Alcuni framework DAL offrono la possibilità di creare la struttura completa del database in base ai file di mapping creati per mappare il dominio sulle tabelle. Per questi framework il modo per testare i repository è spesso quello di creare il database con un database in-memory nella prima fase del test e aggiungere oggetti usando il framework DAL al database in-memory. Successivamente, è possibile utilizzare il repository sul database in memoria per verificare se i metodi funzionano. Questi test sono più lenti, ma molto validi e guidano i tuoi test. Richiede un po 'di collaborazione dal tuo framework DAL.

Opzione 3: test su un database effettivo

Un altro approccio è testare su un database reale e isolare il più semplice. Puoi farlo in diversi modi: circonda i tuoi test con una transazione, pulisci manualmente (non sarebbe molto difficile da mantenere), ricostruisci completamente il database dopo ogni passaggio ... A seconda dell'applicazione che stai costruendo questo può o potrebbe non essere fattibile. Nelle mie applicazioni posso costruire completamente un database di sviluppo locale dal controllo del codice sorgente e i miei unittest sui repository utilizzano le transazioni per isolare completamente i test l'uno dall'altro (transazione aperta, inserimento dati, repository di test, transazione di rollback). Ogni build prima imposta il database di sviluppo locale e quindi esegue unittest isolati per le transazioni per i repository su quel database di sviluppo locale. E'

Non testare il DAL

Se si utilizza un framework DAL come NHibernate, evitare la necessità di testare tale framework. Puoi testare i tuoi file di mapping salvando, recuperando e confrontando un oggetto di dominio per assicurarti che tutto sia a posto (assicurati di disabilitare qualsiasi tipo di memorizzazione nella cache) ma non è così richiesto come molti altri test che dovresti scrivere. Tendo a farlo principalmente per le raccolte sui genitori con condizioni sui bambini.

Durante il test del ritorno dei tuoi repository puoi semplicemente verificare se alcune proprietà identificative sul tuo oggetto dominio corrispondono. Questo può essere un ID ma nei test è spesso più utile controllare una proprietà leggibile dall'uomo. Nel 'fammi vedere tutte le macchine costruite dopo il 2010 ....' questo potrebbe semplicemente verificare che vengano restituite cinque macchine e che le targhe siano 'inserisci la lista qui'. Un ulteriore vantaggio è che ti costringe a pensare all'ordinamento E il tuo test forza automaticamente l'ordinamento. Sareste sorpresi dal numero di applicazioni ordinate più volte (restituite ordinate dal database, ordinate prima di creare un oggetto vista e quindi ordinate l'oggetto vista, tutto sulla stessa proprietà per ogni evenienza ) o assumete implicitamente l'ordinamento del repository e rimuovete accidentalmente che da qualche parte erano lungo la strada, rompendo l'interfaccia utente.

"Unit test" è solo un nome

A mio avviso, i test unitari non dovrebbero per lo più raggiungere il database. Si crea un'applicazione in modo tale che ogni parte di codice che necessita di dati da un'origine lo faccia con un repository e quel repository viene iniettato come dipendenza. Ciò consente un facile derisione e tutta la bontà del TDD che desideri. Ma alla fine vuoi assicurarti che i tuoi repository svolgano i loro compiti e se il modo più semplice per farlo è colpire un database, beh, così sia. Ho da tempo abbandonato l'idea che "i test unitari non dovrebbero toccare il database" e ho appreso che ci sono ragioni molto reali per farlo. Ma solo se puoi farlo automaticamente e ripetutamente. E il tempo che chiamiamo tale test un "test unitario" o un "test di integrazione" è controverso.


3
I test unitari e i test di integrazione hanno scopi diversi. I nomi per questi test non sono puramente decorativi; sono anche descrittivi.
Robert Harvey,

9
  1. Non testare metodi di repository banali o ovvi.

    Se i metodi sono operazioni CRUD banali, tutto ciò che stai davvero testando è se i parametri sono mappati correttamente. Se si dispone di test di integrazione, tali errori saranno comunque immediatamente evidenti.

    Questo è lo stesso principio che si applica alle proprietà banali, come questo:

    public property SomeProperty
    {
        get { return _someProperty; }
        set { _someProperty = value; }
    }
    

    Non lo provi, perché non c'è nulla da testare. Non esiste alcuna convalida o altra logica nella proprietà che deve essere verificata.

  2. Se vuoi ancora testare questi metodi ...

    Le beffe sono il modo per farlo. Ricorda, questi sono test di unità. Non testare il database con unit test; ecco a cosa servono i test di integrazione.

Ulteriori informazioni
The Full Stack, Part 3: Building a Repository using TDD (inizia a guardare a circa 16 minuti).


3
Certo, lo capisco. Tuttavia, se si tratta di un approccio TDD, non dovrei scrivere alcun codice se non ho prima i test per questo codice, giusto?
Thaven,

1
@Thaven - c'è una serie di video su YouTube intitolati "is tdd dead?". Guardali. Affrontano molti punti interessanti, uno dei quali è l'idea che applicare TDD ad ogni livello dell'applicazione non sia necessariamente la migliore idea. "nessun codice senza un test fallito" è una posizione troppo estrema, è una delle conclusioni.
Jules,

2
@Jules: Qual è il tl; dw?
Robert Harvey,

1
@RobertHarvey È difficile riassumere, ma il punto più importante è stato che trattare il TDD come una religione che deve essere sempre osservata è un errore. La scelta di utilizzarlo fa parte di un compromesso e devi considerare che (1) potresti essere in grado di lavorare più velocemente senza di esso su alcuni problemi e (2) potrebbe spingerti verso una soluzione più complessa di quanto ti serva, in particolare se ti ritrovi a usare molte beffe.
Jules,

1
+1 per il punto 1. I test possono essere sbagliati, è solo che di solito sono banali. È inutile testare una funzione la cui correttezza è più ovvia di quella del test. Non è come ottenere una copertura del codice del 100% che ti avvicini ovunque ai test di ogni possibile esecuzione del programma, quindi potresti anche essere intelligente su dove spendi i test.
Doval,
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.