2010-04-28 16 views
7

Come affrontare i test unitari sui metodi privati?C# Design Questions

Ho una classe che carica i dati dei dipendenti in un database. Ecco un esempio:

>

public class EmployeeFacade 
{ 
    public Employees EmployeeRepository = new Employees(); 
    public TaxDatas TaxRepository = new TaxDatas(); 
    public Accounts AccountRepository = new Accounts(); 
    //and so on for about 20 more repositories etc. 

    public bool LoadAllEmployeeData(Employee employee) 
    { 
     if (employee == null) 
      throw new Exception("..."); 


     bool exists = EmployeeRepository.FetchExisting(emps.Id); 
     if (!exists) 
     { 
      EmployeeRepository.AddNew(); 
     } 

     try 
     { 
      EmployeeRepository.Id = employee.Id; 
      EmployeeRepository.Name = employee.EmployeeDetails.PersonalDetails.Active.Names.FirstName; 
      EmployeeRepository.SomeOtherAttribute; 
     } 
     catch() {} 

     try 
     { 
      emps.Save(); 
     } 
     catch(){} 


     try 
     { 
      LoadorUpdateTaxData(employee.TaxData); 
     } 
     catch() {} 

     try 
     { 
     LoadorUpdateAccountData(employee.AccountData); 
     } 
     catch() {} 
     ... etc. for about 20 more other employee objects 

    } 

    private bool LoadorUpdateTaxData(employeeId, TaxData taxData) 
    { 
     if (taxData == null) 
      throw new Exception("..."); 

     ...same format as above but using AccountRepository 

    } 

    private bool LoadorUpdateAccountData(employee.TaxData) 
    { 
     ...same format as above but using TaxRepository 
    } 
} 

vi scrivo un'applicazione per prendere oggetti serializzati (. Per esempio Employee sopra) e caricare i dati nel database.

Ho una domanda di progettazione pochi che vorrei pareri su:

A - sto chiamando questa classe "EmployeeFacade" perché sto (tentando?) Per utilizzare il modello di facciata. È utile praticare il nome del modello sul nome della classe?

B - È consigliabile chiamare le entità concrete delle mie classi di layer DAL "Archivi" ad es. "DipendenteRepository"?

C - Utilizza i repository in questo modo ragionevole o dovrei creare un metodo sul repository stesso per prendere, ad esempio, il Dipendente e quindi caricare i dati da lì, ad es. EmployeeRepository.LoadAllEmployeeData (impiegato dipendente)? Il mio obiettivo è la classe coesiva, ma questo richiederà al repository di conoscere l'oggetto Dipendente che potrebbe non essere buono?

D - C'è un modo carino per non dover controllare se un oggetto è nullo all'inizio di ogni metodo?

E - I ho un repository di dipendenti, un repository fiscale, un repository di account dichiarato pubblico per scopi di verifica dell'unità. Queste sono davvero delle entità private, ma ho bisogno di essere in grado di sostituirle con stub in modo che non scriverà nel mio database (sovraccarico il metodo save() per non fare nulla). C'è comunque intorno a questo o devo esporli?

F - Come posso testare i metodi privati ​​- o è fatto (qualcosa mi dice che non lo è)?

G- "emps.Name = employee.EmployeeDetails.PersonalDetails.Active.Names.FirstName;" questo infrange la Legge di Demetra, ma come posso adeguare i miei oggetti alla legge?

+3

7 domande? !!! –

+0

è una vendita senza lavoro;) – RCIX

+0

Potrebbe essere una buona idea porre questa domanda come 7 domande diverse, piuttosto che raggrupparle tutte insieme. –

risposta

1

Come avvicinarsi al test unitario dei metodi privati?

Non si dovrebbero scrivere test per metodi privati.

L'unico modo possibile di creare metodi privati ​​è un refactoring dei metodi pubblici già testato.

+0

Non significa che tu faccia esattamente come ha fatto? Contrassegnali, test, quindi segna il privato una volta che il test ha avuto successo? – baron

+0

No. Quello che hai detto è concettualmente sbagliato. Quando sviluppi classi seguendo TDD, non ottieni mai metodi non testati. Perché se fai tutto nel modo giusto, allora: 1. crea il test 2. implementa il metodo testato 3. ottieni il metodo della linea verde 4. refactor implementato (qui puoi dividere il metodo in diversi metodi privati ​​/ protetti, che erano ** già testati * * al punto 1) – zerkms

+0

ha senso. Grazie per aver chiarito il messaggio – baron

0

A - Non penso che sia particolarmente male usare il nome del modello nel nome della classe, anche se onestamente non so quante volte è fatto.

F - Penso che lo zerkms abbia ragione, probabilmente devi renderli pubblici, testarli, quindi renderli privati ​​quando sei soddisfatto. Una volta privati, è comunque possibile testare metodi pubblici che utilizzano metodi privati ​​per garantire che continuino a funzionare.

Per quanto riguarda il DAL e simili, suggerirei di esaminare LINQ to SQL, disponibile in .NET 3.0 e versioni successive. È un buon framework per gestire il livello di astrazione tra la tua business logic e il database. Ecco alcuni link per controllare ...

Quick Tutorial for LINQ to SQL in C# Part 1 of Scott Guthrie's blog

Scott Guthrie ha un sacco di roba buona in LINQ, se siete interessati, si dovrebbe controllare di più dei suoi post.

+0

in realtà non intendo quello che hai risposto :-) i metodi privati ​​come di solito ** sono stati estratti ** da metodi pubblici (che sono già coperti da test), ma ** non solo modificati ** da pubblico a privato. – zerkms

4

A - Io non la chiamerei XXXFacade, ma qualcosa di più significativo (che può in realtà significa che si dovrebbe chiamare XXXFacade)

B - io li chiamerei XXXRepository

C - Non capisco molto il tuo modello qui - stai passando in un oggetto Employee e assegnando i suoi valori ai valori equivalenti in EmployeeRepository. Il repository non deve contenere campi di dati - ogni istanza del repository non rappresenta una riga nel database. Il repository è un modo per ottenere dati dentro e fuori dal database, operando su raccolte di entità dal database (es .: il repository è la tabella, le entità sono le righe). Mi aspetto che l'oggetto Repository abbia un metodo Save che accetta un oggetto Employee come parametro e lo mantiene nel database. Come pure un metodo Load che richiede un ID e ritorni e collaboratori:

Employee myEmployee = repository.Load(112345); 
myEmployee.Name = "New Name"; 
repository.Save(myEmployee); 

La classe base Repository non ha bisogno di conoscere la specifica implementazione della classe Employee, attraverso l'uso di farmaci generici e polimorfismo. Dai uno sguardo allo Sh#rpArchitecture per un buon esempio di questo modello.

D - sì, che mettere la logica comune in una classe base astratta (Repository)

E - non renderli pubblici, se essi dovrebbero essere private. Se è necessario utilizzare la logica del repository nei test dell'unità per simulare il recupero dei dati, implementare un'interfaccia comune e quindi prendere in giro l'interfaccia nei test. Non è necessario verificare che il repository restituisca i dati corretti poiché i dati sono transitori e inconsistenti nella realtà. Meglio fingere e testare il tuo comportamento fa ciò che ti aspetti da un archivio fittizio.

F - No. Test di comportamento non implementazione.

G - Non penso che questo problema esista se si esamina la propria architettura come descritto sopra.

+0

Sto utilizzando EntitySpaces Vedi: http://www.developer.entityspaces.net/documentation/Entity/Create.aspx – guazz

+0

OK: quindi le raccolte (es .: EmployeeCollections) sono il repository e Exmployee è l'entità. Se il tuo frammento di codice sopra, è sicuro assumere che il Dipendente che viene passato alla funzione è un modello di visualizzazione da un'interfaccia utente? Sto avendo molti problemi a leggere il tuo codice.Ad esempio, si chiama una variabile EmployeeRepository, tuttavia sembra essere anche un tipo: EmployeeRepository dipendenti pubblici = nuovi dipendenti(); EmployeeRepository emps = new EmployeeRepository(); –

+0

Siamo spiacenti, questo è stato un errore nel codice. L'ho aggiornato. Sì Dipendente è un modello di visualizzazione passato dall'interfaccia utente e deve essere caricato nel database. – guazz

0

A - IMO, sì. Ti ricorda immediatamente lo schema e ti aiuta a capire il codice, e questa è forse una delle pratiche più importanti nella scrittura del codice, lasciando che gli altri capiscano il tuo codice.

B - Preferisco la convenzione xxDAO (oggetto di accesso ai dati).

C - Preferisco la "programmazione orientata al servizio", ovvero un servizio che "sa" per salvare un dipendente e non un "oggetto repository" che mescola tra "modello" e "controllo".

D - Forse usando Aspect, ma non lo consiglio.

E - È possibile creare un'interfaccia per quelle classificate e iniettarle da "fuori" usando setter (proprio come fa la molla), o recuperarle da una specie di fabbrica, in questo modo sarà facile per voi sostituire le classi con finto, e lasciare comunque i membri "privati".

F - Penso che quei metodi dovrebbero essere estratti dal lato del "dipendente di carico" ed essere self service. IMO, dovresti astrarre gli oggetti "dati dei dipendenti" (specialmente se ne hai 20 :-)). e scrivere un servizio semplice che sappia caricare un "oggetto dati dipendente" di qualsiasi tipo.

speranza che ho aiutato,
Shay

1

A - che io chiamo questa classe "EmployeeFacade" perché io sono di utilizzare la facciata modello (di tentare?). È buona prassi denominare lo il modello sul nome della classe?

Non credo che testare i metodi privati ​​sia una buona idea; tuttavia, è possibile testare classi "interne", che sono simili a private nel senso che gli assembly esterni non avranno accesso ad esse, contrassegnandole come Internal Visible al progetto di test dell'unità.

AssemblyInfo.cs - [assembly: InternalsVisibleTo("YourClass.Tests")]

B - è buona per chiamare i concreti entità delle mie classi di livello DAL "Repository" per esempio "EmployeeRepository"?

Lo faccio spesso, non penso che ci sia qualcosa di sbagliato in esso.

C - sta usando il repository in questo modo sensibile o dovrei creare un metodo sul repository stesso per prendere, per esempio, il dipendente e quindi caricare i dati da lì per esempio EmployeeRepository.LoadAllEmployeeData (impiegato dipendente)? Il mio obiettivo è la classe coesiva e, tuttavia, questo richiederà al repository la conoscenza dell'oggetto Dipendente che potrebbe non essere valido?

A meno che non capisco correttamente, li terrei separati. Generalmente utilizzo le mie classi di repository come semplici helper CRUD, scriverò un wrapper attorno al repository che espone le funzionalità necessarie.

D - C'è un modo piacevole giro di non dover controllare se un oggetto è nulla all'inizio di ogni metodo?

Se c'è, io non lo so, vorrei solo usare ArgumentNullException()

E - Ho un EmployeeRepository, TaxRepository, AccountRepository dichiarata come pubblico per unit testing scopo. Queste sono davvero private enità, ma devo essere in grado di sostituire questi con stub in modo che non scriverà al mio database (I sovraccaricare il metodo save() per fare nulla). C'è comunque intorno a questo o devo esporlo?

Vedere la risposta per A, contrassegnarli come Interni e quindi impostare InternalsVisible al proprio gruppo di test dell'unità. Vedi anche MSDN.

F - Come posso testare i metodi privati ​​ - o è fatto (qualcosa mi dice che non lo è)?

In genere non testo i metodi privati ​​e le classi private che devono essere testate contrassegnare come interno e utilizzarle nel mio assieme di test.

+0

Puoi spiegare perché non ti preoccupi di testare i metodi privati? – baron

+0

Si tratta di dettagli di implementazione, che sono stati testati in base al fatto che i metodi pubblici sono stati testati e che i metodi pubblici chiamano i metodi privati ​​oppure sono 'internal' e makred come visibili al mio assembly di test, se necessario. . – Nate

0
  1. La convenzione di denominazione sembra ok.

  2. Chiamando i repository concreti si sta strettamente accoppiando il sistema. Passa loro oggetti pronti contro termine nel costruttore. O utilizzare un contenitore DI/IOC.

  3. Se il repository restituisce un dipendente, ne verrà a conoscenza. Si potrebbe desiderare che il repository conosca il contratto per una classe di dipendenti.

  4. Se si sta ottenendo un valore nullo per qualcosa, è necessario assicurarsi che il codice del provider non invii valori nulli.

  5. È possibile ottenere tale risultato implementando correttamente l'iniezione di dipendenza e utilizzando le interfacce.

  6. I framework di test delle unità standard non vi daranno questo, avrete bisogno di qualcosa come Moles. Un esempio è mostrato su questo post

  7. Se possibile, usa l'ereditarietà più della composizione. Ma se il modello a oggetti lo richiede, allora sei impotente secondo me.