Best practice per lo sviluppo basato su test utilizzando C # e RhinoMocks

Per aiutare il mio team a scrivere codice testabile, 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?

Per massimizzare la testabilità del codice, segui queste regole:

  1. Scrivi prima il test, poi il codice. Motivo: ciò garantisce 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 delle dipendenze. Motivo: non puoi prendere in giro o testare ciò che non può essere visto.

  3. Separare il codice UI dal suo comportamento usando Model-View-Controller o Model-View-Presenter. Motivo: consente di testare la logica aziendale mentre le parti che non possono essere testate (l’interfaccia utente) sono ridotte al minimo.

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

  5. Programma le interfacce, non le classi. Motivo: l’uso delle interfacce chiarisce le relazioni tra gli oggetti. Un’interfaccia dovrebbe definire un servizio che un object ha bisogno dal proprio ambiente. Inoltre, le interfacce possono essere facilmente derise usando Rhino Mock e altri framework di simulazione.

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

  7. Segna come virtuali i metodi che intendi prendere in giro. Motivo: Rhino Mocks non è in grado di simulare metodi non virtuali.

Sicuramente una buona lista. Ecco alcuni pensieri su di esso:

Scrivi prima il test, poi il codice.

Sono d’accordo, ad alto livello. Ma, sarei più specifico: “Scrivi prima un test, poi scrivi il codice sufficiente per superare il test e ripeti”. Altrimenti temerei che i miei test unitari sembrassero più simili a test di integrazione o di accettazione.

Classi di progettazione che utilizzano l’iniezione delle dipendenze.

Concordato. Quando un object 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’object da testare con mock / stub / etc. È così che si testano gli oggetti in isolamento.

Separare il codice UI dal suo comportamento usando 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 ‘ansible testare unitamente un metodo / una class statica senza usare i mock. Quindi, forse questa è una di quelle regole specifiche di Rhino Mock che hai menzionato.

Programma le interfacce, non le classi.

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

Isolare le 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 è particolarmente importante quando la tua squadra non controlla la dipendenza (nota anche come esterna).

Segna come virtuali i metodi che intendi prendere in giro.

Questa è una limitazione di Rhino Mocks. 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:

Usa schemi di progettazione creazionali. Questo aiuterà con DI, ma consente anche di isolare quel codice e testarlo indipendentemente da altre logiche.

Scrivi prove usando la tecnica Arrange / Act / Assert di Bill Wake . Questa tecnica rende molto chiaro quale configurazione è necessaria, cosa viene effettivamente testato e cosa è previsto.

Non aver paura di rotolare i tuoi finti mogli / mocciosi. Spesso, scoprirai che l’utilizzo di mock object framework rende i tuoi 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).

Evita la tentazione di ridefinire la duplicazione dei tuoi test unitari in classi base astratte o metodi di setup / teardown. In questo modo si nasconde il codice di configurazione / pulizia dallo sviluppatore che tenta di eseguire il test dell’unità. In questo caso, la chiarezza di ogni singolo test è più importante della refactoring della duplicazione.

Implementare l’integrazione continua. Accedi al tuo 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 tuo software pulito e completamente integrato.)

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

Dai un’occhiata a questo quickstart per vedere quanto diventano più intuitivi i casi di test, ecco un semplice esempio:

// ShouldExpectMethodCallWithVariable int value = 5; var mock = new Mock(); mock.Expect(x => x.Duplicate(value)).Returns(() => value * 2); Assert.AreEqual(value * 2, mock.Object.Duplicate(value)); 

Conoscere la differenza tra falsi, mock e stub e quando usarli.

Evita di specificare le interazioni usando i mock. Questo rende i test fragili .

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 scadenze e le consegne potrebbero non essere importanti, ma in un ambiente in cui il software è denaro, è fondamentale fare un uso efficace del vostro impegno TDD.

Il TDD è altamente sobject alla legge del ritorno marginale decrescente . 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 nei limiti (blackbox) e occasionalmente nei test whitebox delle aree mission-critical del sistema.

La vera ragione per programmare contro 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 object ha bisogno dal proprio ambiente. Una class fornisce una particolare implementazione di quel servizio. Leggi il libro “Object Design” di Rebecca Wirfs-Brock su ruoli, responsabilità e collaboratori.

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 class 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, pensavo solo a qualcosa che faccio che potresti volere anche tu. Generalmente ho una libreria di test unitari con un dispositivo di test per criterio di class 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 uno stile più BDD . Questo mi consente di scrivere test per specificare cosa dovrebbe fare il metodo e cosa dovrebbe fare l’applicazione in generale.

Eccone un’altra che ho pensato a quello 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 che non conosco. 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.