2009-06-08 7 views
5

DomandaIn TDD e DDD, come gestite le proprietà di sola lettura nei falsi?

Come gestisci i campi di sola lettura durante la creazione di falsi?

Sfondo

Sono nelle fasi principianti di usare ASP.Net MVC e sto usando negozio di articoli sportivi di Steven Sanderson e Nerd Cena di Scott Gu come esempi. Un piccolo problema che ho appena colpito è come lavorare con proprietà di sola lettura quando si fanno falsi. Sto usando LINQToSQL.

mia interfaccia è:

public interface IPersonRespository 
{ 
    Person GetPerson(int id); 
} 

e il mio falso diventa

public class FakePersonRepository 
{ 
    public Person GetPerson(int id) 
    { 
     return new Person {id="EMP12345", name="John Doe", age=47, ssn=123-45-6789, totalDrWhoEpisodesWatched=42}; 
    } 
} 

Ecco il mio problema. I campi id, ssn e totalDrWhoEpisodesWatched sono di sola lettura, quindi il codice sopra non funzionerà. Tuttavia, non riconosco come creare una persona nuova falsa e impostare una proprietà di sola lettura. Sono sicuro che ci sia una soluzione, ma non l'ho ancora trovata nelle mie ricerche.

Aggiornamento: ereditarietà + proprietà che si nasconde come soluzione potenziale?

Non ho ancora deciso una soluzione risoluta al problema. Non mi piace la nozione di modificare le mie classi Dominio allo scopo di creare falsi. Per me, l'aggiunta di markup alle classi di dominio per fare test è una forma di accoppiamento aggiunto - l'accoppiamento con l'implementazione del test. Ora sto studiando un'altra possibilità, ovvero creare una classe FakePerson, che eredita da Person, ma nasconde le proprietà con nuove proprietà di lettura-scrittura.

public class FakePerson: Person 
{ 
    public new int age { get; set; } 
    public new string ssn { get; set; } 
    public new int totalDrWhoEpisodesWatched { get; set; } 
} 

Finora, questa soluzione è come mi sto appoggiando. Rompe il principio di sostituzione di Liskov, tuttavia questo non mi infastidisce più di tanto in un progetto di test. Sarei lieto di ascoltare qualsiasi critica e/o feedback su questo come soluzione.

Vincitore: Mock Frameworks

Moq sembra fare il lavoro. La mia ultima soluzione di nascondere la proprietà attraverso l'ereditarietà, in effetti, funziona, tuttavia utilizzando Moq, ottengo un insieme standardizzato di funzionalità che è più gestibile. Presumo che altri framework mock abbiano questa funzionalità, ma non ho controllato. Si dice che Moq sia più semplice per l'inizio della finta scrittura, cosa che sicuramente sono in questo momento.

+0

Perché è impostato per essere in sola lettura? –

+0

È impostato su sola lettura, poiché nessun utente del dominio ha i diritti per modificare le proprietà. Potrebbe essere per una serie di motivi, ma spesso, poiché l'applicazione non ha i diritti per modificare il campo perché è un campo di calcolo/generato automaticamente o a causa delle politiche di sicurezza del database. – John

+3

Su un argomento leggermente non correlato, consigliamo di modificare i nomi delle proprietà in PascalCase per rispettare le convenzioni di denominazione .NET. http://msdn.microsoft.com/en-us/library/ms229043(loband).aspx –

risposta

6

Considera prendere in giro il tipo Persona nel test. Esempio con Moq:

var mock = new Mock<Person>(); 
mock.SetupGet(p => p.id).Returns("EMP12345"); 
mock.SetupGet(p => p.ssn).Returns("123-45-6789"); 
mock.SetupGet(p => p.totalDrWhoEpisodesWatched).Returns(42); 
return mock.Object; 

In caso contrario, provare a scoprire come LINQ to SQL definisce quelle di sola lettura proprietà.

EDIT: se si tenta il sopra e Moq genera ArgumentException nel SetupGet chiamata con il messaggio "l'installazione non valido su un membro non-override: p => p.id", allora avete bisogno di segnare la proprietà come virtuale. Questo dovrà essere fatto per ogni proprietà il cui getter desideri ignorare.

In LINQ to SQL, questo può essere fatto in sala operatoria progettista selezionando la proprietà, quindi nella finestra Proprietà impostare Inheritance modificatore a virtuale.

+0

È possibile bypassare i puntelli readonly? Ricordo che ho avuto problemi con i mocciosi di Rhino. :/ –

+0

Ti stai riferendo a "campi" di sola lettura, o proprietà senza setter accessibili pubblicamente? Nel primo caso non penso che Moq possa aiutare. In quest'ultimo caso, i setter non contano - Moq sovrascrive il getter. – mvr

+0

Proprietà di sola lettura (ovvero proprietà con set). Da Linq a SQL imposta la variabile privata nella classe Person, che in questo esempio sarebbe _id, _ssn e _totalDrWhoEpisodesWatched. – John

1

È possibile impostare solo proprietà readonly nel costruttore della classe. L'oggetto Persona dovrebbe avere un costruttore che accetta id, ssn e totalDrWhoEpisodesWatched. Naturalmente, se questo è un oggetto generato da linqtosql, potresti avere problemi a modificarlo mentre il codice viene generato automaticamente.

Si potrebbe considerare l'utilizzo di un oggetto mappato per esporre nel proprio repository ... in modo tale che non si debba mai effettivamente usare il proprio oggetto linqtosql come modello.

+0

@ Joel- Trovi il sovraccarico dell'utilizzo di un modello POCO che merita lo sforzo extra richiesto dalla doppia mappatura? – RichardOD

+0

Non mi piace l'idea di aggiungere ssn e totalDrWhoEpisodesWatched al costruttore. Aggiungere parametri aggiuntivi che non sono effettivamente necessari nel programma in esecuzione solo per eseguire test sembra contrario alla buona progettazione dell'API, perché significa rendere pubblico un metodo che non dovrebbe essere utilizzato dai consumatori (a parte un consumatore di test). Cosa intendi per "oggetto mappato"? – John

+0

@RichardOD, a volte :-) @John, per oggetto mappato, intendo utilizzare un oggetto che hai codificato tu stesso (invece di fare affidamento sulla classe generata automaticamente fornita da linq2sql). Pertanto, quando si seleziona dall'archivio dati, si farebbe (selezionare new MyPerson() {...}) e impostare le proprietà lì. Ciò consente di evitare l'affidamento su una classe autogenerata che potrebbe cambiare o meno in futuro. –

1

In .NET, è possibile contrassegnare i setter come "interni" e utilizzare l'attributo di montaggio InternalsVisibleTo per rendere visibili i componenti interni all'assieme di test. In questo modo i tuoi setter non saranno pubblici, ma puoi comunque accedervi.

nota: anche se la domanda non è contrassegnata.NET, ho pensato che fosse basato sul tuo uso della sintassi di inizializzazione dell'oggetto. Se la mia ipotesi è sbagliata, questo suggerimento non si applica (a meno che la lingua che stai usando abbia una funzione equivalente, ovviamente).

+0

Non l'ho segnato .Net, comunque ho pensato che le citazioni di ASP.net e LinqToSQL lo avrebbero dato. Grazie. – John

0

Se è per le prove, prendere in considerazione l'uso della riflessione. Ciò non implicherebbe problemi nel tuo modello di dominio.

Ad esempio, ho ottenuto la classe FactoryBase, che utilizza la riflessione per impostare l'elica necessaria mediante l'espressione lambda tramite parametri (come this). Funziona come un incantesimo: la creazione di una nuova fabbrica è semplice come definizione del tipo di repository e dei dati di entità predefiniti.

+0

La risposta è intrigante, ma non vedo come risolva il problema che ho chiesto, ovvero come gestire le proprietà di sola lettura nei fake. Foo ha setter per tutte e 3 le proprietà (Bar, Baz e Bling). Cosa faresti se BAZ avesse solo un getter? – John

+0

Reflection può gestirlo. Ho anche alcuni oggetti di dominio con setter protetti e privati. –

0

Uso anche il moq. Lo adoro e funziona benissimo. Ma, prima di iniziare a usare Moq, ho scritto molti falsi. Ecco come avrei risolto il problema usando i falsi.

Poiché un falso può avere metodi aggiuntivi che l'implementazione di "produzione" non ha, aggiungerei alcuni metodi aggiuntivi alla mia falsa implementazione per gestire l'impostazione della porzione di sola lettura.

Ti piace questa:

public class FakePersonRepository : IPersonRespository 
{ 
    private IDictionary<int, Person> _people = new Dictionary<int, Person>(); 

    public Person GetPerson(int id) // Interface Implementation 
    { 
     return _people(id); 
    } 

    public void SetPerson(int id, Person person) // Not part of interface 
    { 
     _people.Add(id, person); 
    } 

} 
Problemi correlati