2011-02-01 27 views
9

Mi chiedo come aggirare questo. Sto usando Nibernato e fluente.Unit test e nibernate?

ho una classe di dominio come questo

public class User 
{ 
    public virtual int UserId {get; private set;} 
} 

questa sembra essere la convenzione quando si fa NHibernate perché impedisce alle persone di impostazione e id come si è auto generato.

Ora il problema si presenta quando sono un test unitario.

Ho un codice reptivo in un repository che sto prendendo in giro, quindi sto solo testando il mio livello di servizio. Il problema arriva quando succede.

User user = repo.GetUser(email); 

questo dovrebbe restituire un oggetto utente.

quindi voglio usare moq per fare questo

repo.Setup(x => x.GetUser(It.IsAny<string>())).Return(/* UserObject here */) 

ora qui è il problema

ho bisogno di fare l'oggetto d'uso e metterlo nella parte di ritorno.

quindi vorrei fare qualcosa di simile

User user = new User() 
{ 
    UserId = 10, 
} 

Ma questo è dove sta il problema che ho bisogno di impostare l'ID, perché io in realtà lo uso in seguito per fare un po 'LINQ su alcune collezioni (nel livello di servizio come non sta colpendo il mio db quindi non dovrebbe essere nel mio repository) quindi ho bisogno di averlo impostato ma non posso impostarlo perché è un set privato.

Quindi cosa dovrei fare? Dovrei semplicemente rimuovere il privato o c'è un altro modo?

risposta

14

Si può avere il falso Repository oggetto restituisce un falso User oggetto:

var stubUser = new Mock<User>(); 
stubUser.Setup(s => s.UserId).Returns(10); 

var stubRepo = new Mock<IUserRepository>(); 
stubRepo.Setup(s => s.GetUser(It.IsAny<string>())).Return(stubUser); 

Ci sono un paio di cose da osservare qui:

  1. Moq può solo i membri falsi di classi concrete, se sono contrassegnati come virtuali. Questo potrebbe non essere applicabile in alcuni scenari, nel qual caso l'unico modo per fingere un oggetto tramite Moq è di implementare un'interfaccia.
    In questo caso, tuttavia, la soluzione funziona correttamente perché NHibernate already imposes the same requirement sulle proprietà della classe User per eseguire il caricamento lazy.
  2. La presenza di oggetti falsi che restituiscono altri falsi può a volte portare a test unitari più specifici. In queste situazioni, la costruzione di modelli di oggetti ricchi costituiti da matrici e mazze cresce fino al punto in cui diventa difficile determinare cosa esattamente viene testato, rendendo il test stesso illeggibile e difficile da mantenere. È una pratica di prova unitaria perfettamente precisa, per essere chiara, ma deve essere usata consapevolmente.

Risorse correlate:

+0

Grazie questo funziona solo perfettamente. Potrei avere i sintomi del numero 2. Devo solo prendere in giro tante cose. Sto cercando di mantenere anche solo ciò che ho bisogno di testare. Ad esempio, non mockò un userName per l'utente poiché non è usato nel metodo. Mi piacerebbe provare il moq di autofixture ma non riesco a capire come usare veramente e sembra che non ci siano tutorial reali su di esso, quindi non posso determinare se questo mi possa aiutare o no. – chobo2

2

risposta di Enrico è il posto giusto per unit testing. Offro un'altra soluzione perché questo problema si verifica anche in altre circostanze, dove potresti non voler utilizzare Moq. Uso regolarmente questa tecnica nel codice di produzione in cui il modello di utilizzo comune è per un membro della classe di sola lettura, ma alcune altre classi devono modificarlo. Un esempio potrebbe essere un campo di stato, che in genere è di sola lettura e deve essere impostato solo da una macchina a stati o da una classe logica aziendale.

Fondamentalmente si fornisce l'accesso al membro privato attraverso una classe nidificata statica che contiene un metodo per impostare la proprietà. Un esempio vale più di mille parole:

public class User { 
    public int Id { get; private set; } 

    public static class Reveal { 
     public static void SetId(User user, int id) { 
      user.Id = id; 
     } 
    } 
} 

si utilizza in questo modo:

User user = new User(); 
User.Reveal.SetId(user, 43); 

Naturalmente, questo poi permette a chiunque di impostare il valore della proprietà quasi facilmente come se si fosse fornito un pubblico setter. Ma ci sono alcuni vantaggi con questa tecnica:

  • non Intellisense richiesta di conferma per il setter di proprietà o di un metodo setId()
  • i programmatori devono utilizzare in modo esplicito la sintassi strano per impostare la proprietà con la classe Reveal, loro in tal modo spingendo che non dovrebbero probabilmente essere facendo questo
  • si possono facilmente eseguire l'analisi statica sul usi della classe Reveal per vedere quale codice è bypassando i modelli di accesso standard

Se sei solo alla ricerca t o modificare una proprietà privata per scopi di testing unitario, e si è in grado di moqare l'oggetto, quindi consiglio comunque il suggerimento di Enrico; ma potresti trovare questa tecnica utile di volta in volta.

+0

+1. Un sacco di opzioni simili - InternalsVisibleToAttribute, protected e subclass, crack con reflection, put SetId su User con ObsoleteAttribute con testo di avviso appropriato, passate in Id al costruttore sovraccarico (la mia preferenza quando possibile), ecc. Mi piace perché incoraggia i test con il pocos/DTOS. Per quanto io amo Moq, @ Enrico ha ragione sui pericoli della sovra-specifica. – TrueWill

0

Invece di provare a prendere in giro il tuo repository, ti suggerirei di provare a utilizzare un database SQLite in memoria per il test. Ti darà la velocità che stai cercando e renderà le cose molto più semplici. Se vuoi vedere un esempio funzionante puoi dare un'occhiata a uno dei miei progetti GitHub: https://github.com/dlidstrom/GridBook.

1

Un'altra alternativa se si preferisce non prendere in giro le proprie classi di entità è impostare l'ID privato/protetto mediante la riflessione.

Sì, so che questo di solito non è considerato molto favorevolmente, e spesso citato come un segno di design scadente da qualche parte. Ma in questo caso, avere un ID protetto sulle entità di NHibernate è il paradigma standard, quindi sembra una soluzione abbastanza ragionevole.

Possiamo provare ad implementarlo bene almeno. Nel mio caso, il 95% delle mie entità usano tutti un singolo Guid come identificativo univoco, con pochi che usano un numero intero.Pertanto le nostre classi di entità di solito implementano una semplice HasID interfaccia:

public interface IHasID<T> 
{ 
    T ID { get; } 
} 

In una classe di entità reale, potremmo implementare in questo modo:

public class User : IHasID<Guid> 
{ 
    Guid ID { get; protected set; } 
} 

Questo ID è mappato NHibernate come chiave primaria in il solito modo.

Per l'impostazione di questa nei nostri test di unità, siamo in grado di utilizzare questa interfaccia per fornire un metodo di estensione a portata di mano:

public static T WithID<T, K>(this T o, K id) where T : class, IHasID<K> 
{ 
    if (o == null) return o; 
    o.GetType().InvokeMember("ID", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, o, new object[] { id }); 
    return o; 
} 

Non avere l'interfaccia chassid per fare questo, ma significa possiamo saltare un po 'di codice in più, ad esempio non è necessario controllare se l'ID è effettivamente supportato o meno.

Il metodo di estensione restituisce anche l'oggetto originale, in modo da nell'uso di solito solo di catene, sull'estremità del costruttore:

var testUser = new User("Test User").WithID(new Guid("DC1BA89C-9DB2-48ac-8CE2-E61360970DF7")); 

o addirittura, dal momento che per i GUID non mi interessa quello che la realtà ID è, ho un altro metodo di estensione:

public static T WithNewGuid<T>(this T o) where T : class, IHasID<Guid> 
{ 
    if (o == null) return o; 
    o.GetType().InvokeMember("ID", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, o, new object[] { Guid.NewGuid() }); 
    return o; 
} 

E in uso:

var testUser = new User("Test User").WithNewGuid();