2008-09-23 12 views
86

Per aiutare il mio team a scrivere codice verificabile, ho trovato questo semplice elenco di best practice per rendere più verificabile la nostra base di codice C#. (Alcuni punti si riferiscono alle limitazioni di Rhino Mocks, una struttura di derisione per C#, ma le regole possono essere applicate anche in generale.) Qualcuno ha delle migliori pratiche che seguono?Best practice per lo sviluppo basato su test con C# e RhinoMocks

per massimizzare la verificabilità del codice, seguire queste regole:

  1. Scrivi la prima prova, poi il codice. Motivo: ciò assicura che si scriva codice verificabile e che ogni riga di codice ottenga i test scritti per essa.

  2. Classi di progettazione che utilizzano l'iniezione dipendente. Motivo: non puoi prendere in giro o testare ciò che non può essere visto.

  3. Separare il codice UI dal suo comportamento utilizzando Model-View-Controller o Model-View-Presenter. Motivo: consente di verificare la logica aziendale mentre le parti che non possono essere testate (l'interfaccia utente) sono ridotte a icona.

  4. Non scrivere metodi o classi statici. Motivo: i metodi statici sono difficili o impossibili da isolare e Rhino Mock non è in grado di prenderli in giro.

  5. Interfaccia di programma, non classi. Motivo: l'utilizzo delle interfacce consente di chiarire le relazioni tra gli oggetti. Un'interfaccia dovrebbe definire un servizio che un oggetto ha bisogno dal proprio ambiente. Inoltre, le interfacce possono essere facilmente derise usando Rhino Mock e altri framework di simulazione.

  6. Isolare dipendenze esterne. Motivo: non è possibile testare le dipendenze esterne non risolte.

  7. Contrassegnare come virtuali i metodi che si intende deridere. Motivo: Rhino Mocks non è in grado di simulare metodi non virtuali.

+0

Questa è una lista utile. Attualmente stiamo usando NUnit e Rhino.Mocks, ed è bene precisare questi criteri per i membri del team che hanno meno familiarità con questo lato del test delle unità. –

risposta

56

Definitivamente una buona lista. Ecco alcune riflessioni:

Scrivere prima il test, quindi il codice.

Sono d'accordo, ad alto livello.Ma, sarei più specifico: "Scrivi prima un test, quindi scrivi quel tanto che basta codice per superare il test e ripetere". Altrimenti temerei che i miei test unitari sembrassero più simili a test di integrazione o di accettazione.

Classi di progettazione che utilizzano l'iniezione dipendente.

Concordato. Quando un oggetto crea le sue dipendenze, non hai alcun controllo su di esse. Inversione di controllo/Iniezione delle dipendenze ti dà quel controllo, permettendoti di isolare l'oggetto da testare con mock/stub/etc. È così che si testano gli oggetti in isolamento.

Separare il codice dell'interfaccia utente dal suo comportamento utilizzando Model-View-Controller o Model-View-Presenter.

Concordato. Si noti che anche il presentatore/controller può essere testato usando DI/IoC, passandogli una vista e un modello stubbed/mocked. Dai un'occhiata a Presenter First TDD per ulteriori informazioni.

Non scrivere metodi o classi statici.

Non sono sicuro di essere d'accordo con questo. E 'possibile testare unitamente un metodo/una classe statica senza usare i mock. Quindi, forse questa è una di quelle regole specifiche di Rhino Mock che hai menzionato.

Interfaccia di programma, non classi.

Sono d'accordo, ma per un motivo leggermente diverso. Le interfacce offrono una grande flessibilità allo sviluppatore del software, oltre al semplice supporto per vari framework di oggetti mock. Ad esempio, non è possibile supportare correttamente DI senza interfacce.

Isolare dipendenze esterne.

Concordato. Nascondere le dipendenze esterne dietro la propria facciata o adattatore (a seconda dei casi) con un'interfaccia. Ciò ti consentirà di isolare il tuo software dalla dipendenza esterna, che si tratti di un servizio Web, una coda, un database o altro. Questo è specialmente importante quando il tuo team non controlla la dipendenza (a.k.a. external).

Contrassegnare come virtuale i metodi che si intende deridere.

Questa è una limitazione di Rhino Mock. In un ambiente che preferisce gli stub codificati a mano su una struttura di oggetti fittizi, ciò non sarebbe necessario.

E, un paio di nuovi punti da considerare:

Utilizzare i design pattern creazionale. Questo aiuterà con DI, ma consente anche di isolare quel codice e testarlo indipendentemente dalle altre logiche.

Scrivere i test utilizzando Bill Wake's Arrange/Act/Assert technique. Questa tecnica rende molto chiaro quale configurazione è necessaria, cosa viene effettivamente testato e cosa è previsto.

Non aver paura di rotolare i tuoi finti mogli. Spesso, si scopre che l'utilizzo di framework di oggetti mock rende i test incredibilmente difficili da leggere. Ruotando il tuo, avrai il controllo completo sui tuoi mock/stub e sarai in grado di mantenere i tuoi test leggibili. (Fare riferimento al punto precedente)

Evitare la tentazione di rifattorizzare la duplicazione dei propri test di unità in classi di base astratte o metodi di setup/teardown. In questo modo si nasconde il codice di configurazione/pulizia dallo sviluppatore che tenta di annullare il test dell'unità. In questo caso, la chiarezza di ogni singolo test è più importante della refactoring della duplicazione.

Implementazione integrazione continua. Verifica il codice su ogni "barra verde". Crea il tuo software ed esegui la tua suite completa di test unitari ad ogni check-in. (Certo, questa non è una pratica di codifica, di per sé, ma è uno strumento incredibile per mantenere il software pulito e completamente integrato.)

+2

Di solito trovo che se un test è difficile da leggere, non è colpa del framework ma del codice che sta testando. Se il SUT è complicato da configurare, allora forse dovrebbe essere suddiviso in più concetti. –

1

Buona lista. Una delle cose che potresti voler stabilire - e non posso darti molti consigli da quando sto iniziando a pensarci da solo - è quando una classe dovrebbe trovarsi in una libreria, uno spazio dei nomi, uno spazio dei nomi annidato. Potresti persino voler calcolare in anticipo un elenco di librerie e spazi dei nomi e imporre che il team si debba incontrare e decidere di unire due/aggiungerne uno nuovo.

Oh, ho appena pensato a qualcosa che faccio che potresti desiderare anche tu. Generalmente ho una libreria di test unitari con un dispositivo di test per criterio di classe in cui ogni test va in uno spazio dei nomi corrispondente. Tendo anche ad avere un'altra libreria di test (test di integrazione?) Che è in un altro BDD style. Questo mi consente di scrivere test per specificare cosa dovrebbe fare il metodo e cosa dovrebbe fare l'applicazione in generale.

+0

Eseguo anche una sezione di test di stile BDD simile (oltre al codice di test delle unità) in un progetto personale. –

6

Conoscere la differenza tra fakes, mocks and stubs e quando utilizzarli.

Evitare di specificare le interazioni utilizzando i mock. Questo rende i test brittle.

10

Se si sta lavorando con .Net 3.5, si potrebbe voler esaminare la libreria di simulazioni Moq - utilizza alberi di espressione e lambda per rimuovere l'idioma non-intuitivo di risposta al record della maggior parte delle altre librerie di derisione.

Partenza questo quickstart per vedere quanto più intuitivo vostri test diventano, ecco un semplice esempio:

// ShouldExpectMethodCallWithVariable 
int value = 5; 
var mock = new Mock<IFoo>(); 

mock.Expect(x => x.Duplicate(value)).Returns(() => value * 2); 

Assert.AreEqual(value * 2, mock.Object.Duplicate(value)); 
+5

Penso che la nuova versione di Rhino Mocks funzioni in questo modo –

0

Ecco un altro che ho pensato che mi piace fare.

Se si prevede di eseguire test dall'unità di test Gui anziché da TestDriven.Net o NAnt, ho trovato più semplice impostare il tipo di progetto di test dell'unità sull'applicazione console anziché sulla libreria. Ciò consente di eseguire i test manualmente e di passarli attraverso la modalità di debug (che il suddetto TestDriven.Net può effettivamente fare per te).

Inoltre, mi piace sempre avere un progetto Playground aperto per testare bit di codice e idee con cui non ho familiarità. Questo non dovrebbe essere controllato nel controllo del codice sorgente. Ancora meglio, dovrebbe trovarsi in un repository separato per il controllo del codice sorgente solo sulla macchina dello sviluppatore.

3

Questo è un post molto utile!

Vorrei aggiungere che è sempre importante capire il contesto e il sistema sotto test (SUT). Seguire i principi di TDD alla lettera è molto più semplice quando si scrive nuovo codice in un ambiente in cui il codice esistente segue gli stessi principi. Ma quando si scrive nuovo codice in un ambiente legacy non TDD, si scopre che gli sforzi del TDD possono rapidamente andare oltre le previsioni e le aspettative.

Per alcuni di voi, che vivono in un mondo interamente accademico, le tempistiche e la consegna potrebbero non essere importanti, ma in un ambiente in cui il software è denaro, è fondamentale fare un uso efficace dello sforzo TDD.

TDD è altamente soggetto alla legge Diminishing Marginal Return. In breve, i tuoi sforzi verso TDD sono sempre più preziosi finché non raggiungi un punto di massimo ritorno, dopo di che, il tempo successivo investito in TDD ha sempre meno valore.

Tendo a credere che il valore primario di TDD sia in boundary (blackbox) così come in occasionali test whitebox delle aree mission-critical del sistema.

2

La vera ragione per programmare le interfacce non è rendere la vita più facile a Rhino, ma chiarire le relazioni tra gli oggetti nel codice. Un'interfaccia dovrebbe definire un servizio che un oggetto ha bisogno dal proprio ambiente. Una classe fornisce una particolare implementazione di quel servizio. Leggi il libro "Object Design" di Rebecca Wirfs-Brock su ruoli, responsabilità e collaboratori.

+0

D'accordo ... Ho intenzione di aggiornare la mia domanda per riflettere questo. –