2010-10-01 8 views
39

La lettura this question mi ha aiutato a consolidare alcuni dei problemi che ho sempre avuto con i test unitari, TDD, et al.Schemi di progettazione utili per il test dell'unità/TDD?

Da quando ho scoperto l'approccio allo sviluppo TDD, sapevo che era la strada giusta da seguire. La lettura di vari tutorial mi ha aiutato a capire come iniziare, ma sono sempre stati molto semplicistici - non proprio qualcosa che si può applicare a un progetto attivo. Il meglio che ho gestito è scrivere test su piccole parti del mio codice - cose come le librerie, che sono usate dall'app principale ma non sono integrate in alcun modo. Mentre questo è stato utile, equivale a circa il 5% del codice base. C'è molto poco là fuori su come andare al prossimo passo, per aiutarmi a fare alcuni test nell'app principale.

Commenti come "Most code without unit tests is built with hard dependencies (i.e.'s new's all over the place) or static methods." e "...it's not rare to have a high level of coupling between classes, hard-to-configure objects inside your class [...] and so on." mi hanno fatto capire che il prossimo passo è capire come disaccoppiare il codice per renderlo testabile.

Cosa dovrei guardare per aiutarmi a fare questo? Esiste una serie specifica di schemi di progettazione che devo comprendere e iniziare a implementare e che consentirà test più semplici?

risposta

45

Qui Mike Clifton descrive 24 modelli di prova dal 2004. È un'euristica utile durante la progettazione dei test di unità.

http://www.codeproject.com/Articles/5772/Advanced-Unit-Test-Part-V-Unit-Test-Patterns

Passato/Fallito Patterns

Questi modelli sono la prima linea di difesa (o di attacco, a seconda della prospettiva) per garantire un buon codice. Ma attenzione, sono ingannevoli in quello che ti dicono del codice.

  • Il modello-Test Semplice
  • Il modello Codice-Path
  • il parametro della gamma del modello

modelli di transazione dati

modelli di transazione dei dati sono un inizio a abbracciando le questioni relative alla persistenza e alla comunicazione dei dati. Ulteriori informazioni su questo argomento sono discusse in "Modelli di simulazione". Inoltre, questi modelli omettono intenzionalmente il test di stress, ad esempio il caricamento sul server. Questo sarà discusso in "Modelli di stress test".

  • Il/O semplice schema-Data-I
  • il vincolo-Data modello
  • il rollback del modello

Patterns Collection Management

Molto di ciò che le applicazioni fanno è gestire raccolte di informazioni. Mentre ci sono una varietà di collezioni disponibili per il programmatore, è importante verificare (e quindi documentare) che il codice stia usando la raccolta corretta. Questo influenza ordini e vincoli.

  • Il modello Collection-Order
  • Il modello Enumeration Il modello
  • Collection-Constraint
  • Il modello Collection-indicizzazione

Patterns prestazioni

Unità di test dovrebbe non riguardare solo la funzione ma anche con la forma. Quanto efficientemente funziona il codice in prova? Quanto velocemente? Quanta memoria usa? Scambia l'inserimento dei dati per il recupero dei dati in modo efficace? Libera correttamente le risorse? Queste sono tutte cose che sono sotto la supervisione dei test unitari. Includendo i modelli di prestazioni nel test di unità, l'implementatore ha un obiettivo da raggiungere, il che si traduce in codice migliore, un'applicazione migliore e un cliente più felice.

  • Il modello Performance-test

modelli di processo

Unit testing ha lo scopo di verificare, anche, unità ... le funzioni di base dell'applicazione. Si può affermare che i processi di test dovrebbero essere relegati alle procedure di test di accettazione, tuttavia non prendo in considerazione questo argomento. Un processo è solo un diverso tipo di unità. I processi di test con un tester unitario offrono gli stessi vantaggi degli altri test unitari: documentano il modo in cui il processo è destinato a funzionare e il tester dell'unità può aiutare l'implementatore testando anche il processo fuori sequenza, identificando rapidamente i potenziali problemi dell'interfaccia utente come bene. Il termine "processo" include anche le transizioni di stato e le regole aziendali, che devono essere entrambe convalidate.

  • Il modello di processo-Sequence
  • Il modello di processo-Stato
  • Processo regola modello

modelli di simulazione

transazioni di dati sono difficili da testare perché spesso richiede una configurazione preimpostata, una connessione aperta e/o un dispositivo online (per citarne alcuni). Gli oggetti simulati possono venire in soccorso simulando il database, il servizio Web, l'evento utente, la connessione e/o l'hardware con cui il codice sta eseguendo transazioni. oggetti mock hanno anche la capacità di creare condizioni di errore che sono molto difficili da riprodurre nel mondo reale - un collegamento con perdita di dati, un server lento, un hub di rete fallito, ecc

  • modello Mock-Object
  • il Service-simulazione del modello
  • lo schema di bit-Error-simulazione
  • il componente-modello di simulazione

Patterns multithreading

Le applicazioni multithreading di test di unità sono probabilmente una delle cose più difficili da fare perché è necessario impostare una condizione che per sua stessa natura è destinata ad essere asincrona e quindi non deterministica. Questo argomento è probabilmente un grande articolo in sé, quindi fornirò solo un modello molto generico qui. Inoltre, per eseguire correttamente molti test filettatura, l'applicazione dell'unità tester deve eseguire stesso test come thread separati in modo che il tester unità non è attivato quando un thread finisce in uno stato di attesa

  • Il pattern Signaled
  • il modello Deadlock risoluzione

modelli di stress-test

maggior parte delle applicazioni sono testati in ambienti ideali - il programmatore sta usando una machin veloce e con poco traffico di rete, utilizzando piccoli set di dati. Il mondo reale è molto diverso. Prima che qualcosa si rompa completamente, l'applicazione potrebbe subire un degrado e rispondere male o con errori all'utente. I test unitari che verificano le prestazioni del codice in condizioni di stress devono essere soddisfatti con un fervore uguale (se non superiore) rispetto ai test unitari in un ambiente ideale.

  • Il Bulk-Data-Stress-Test Pattern
  • Resource-Stress-Test Pattern
  • il carico-Test Pattern

Patterns Presentation Layer

Uno dei gli aspetti più impegnativi del collaudo unitario sono la verifica che le informazioni arrivino all'utente esattamente al livello di presentazione stesso e che il lavoro interno i re dell'applicazione stanno impostando correttamente lo stato del livello presentazione.Spesso, i livelli di presentazione sono impigliati con oggetti business, oggetti dati e logica di controllo. Se stai pianificando di testare il livello di presentazione delle unità, devi comprendere che una separazione netta tra preoccupazioni è obbligatoria. Parte della soluzione implica lo sviluppo di un'architettura Model-View-Controller (MVC) appropriata. L'architettura MVC fornisce un mezzo per sviluppare buone pratiche di progettazione quando si lavora con il livello di presentazione. Tuttavia, è facilmente abusato. È necessaria una certa dose di disciplina per garantire che si stia effettivamente implementando l'architettura MVC correttamente, piuttosto che solo nella sola parola.

  • The View-Stato Test Pattern
  • Il Modello-Stato Test Pattern
2

I modelli di progettazione non sono direttamente rilevanti per TDD, in quanto sono dettagli di implementazione. Non dovresti provare ad adattare i pattern al tuo codice solo perché esistono, ma piuttosto tendono ad apparire mentre il tuo codice si evolve. Diventano anche utili se il tuo codice è maleodorante, dal momento che aiutano a risolvere tali problemi. Non sviluppare il codice con i modelli di progettazione in mente, basta scrivere il codice. Quindi ottenere i test di passaggio e refactoring.

1

Un sacco di problemi come questo possono essere risolti con incapsulamento corretto. Oppure potresti avere questo problema se stai mescolando le tue preoccupazioni. Supponiamo che tu abbia il codice che convalida un utente, convalida un oggetto dominio, quindi salva l'oggetto dominio tutto in un metodo o classe. Hai mescolato le tue preoccupazioni e non sarai felice. È necessario separare queste preoccupazioni (autenticazione/autorizzazione, logica aziendale, persistenza) in modo da poterle testare isolatamente.

Gli schemi di progettazione aiutano, ma molti di quelli esotici hanno problemi molto stretti a cui possono essere applicati. Pattern come composite, command, sono usati spesso e sono semplici.

La linea guida è: se è molto difficile testare qualcosa, è probabile che lo si possa rifattorizzare in problemi più piccoli e testare i bit refactored in isolamento. Quindi, se hai un metodo a 200 righe con 5 livelli di istruzioni if ​​e alcuni for-loop, potresti voler rompere quel ventoso.

Quindi, iniziare vedendo se è possibile semplificare il complicato codice separando le preoccupazioni, e quindi vedere se è possibile semplificare il codice complicato suddividendolo. Naturalmente se un modello di progettazione ti salta addosso, allora vai a prenderlo.

9

direi che è necessario principalmente due cose da provare, e vanno di pari passo:

  • interfacce, interfacce, interfacce
  • iniezione di dipendenza; questo in congiunzione con le interfacce ti aiuterà a scambiare parti a tuo piacimento per isolare i moduli che vuoi testare. Vuoi testare il tuo sistema simile a cron che invia notifiche ad altri servizi? installalo e sostituisci l'implementazione del codice reale per tutto il resto con componenti che obbediscono all'interfaccia corretta ma sono cablati per reagire nel modo in cui vuoi testare: notifica via mail? prova cosa succede quando il server smtp è inattivo lanciando un'eccezione

Io stesso non ho padroneggiato l'arte del collaudo di unità (e sono lontano da esso), ma questo è dove i miei sforzi principali stanno andando attualmente .Il problema è che non continuo a progettare per i test e, di conseguenza, il mio codice deve piegarsi all'indietro per accogliere ...

6

Il libro di Michael Feather Working Effectively With Legacy Code è esattamente quello che stai cercando. Definisce il codice legacy come "codice senza test" e parla di come ottenerlo sotto test.

Come con la maggior parte delle cose è un passo alla volta. Quando apporti una modifica o una correzione, prova ad aumentare la copertura del test. Con il passare del tempo avrai una serie di test più completa. Parla di tecniche per ridurre l'accoppiamento e come adattare i pezzi di prova tra la logica dell'applicazione.

Come notato in altre risposte, l'iniezione di dipendenza è un buon modo per scrivere codice verificabile (e genericamente non abbinato in generale).

3

Modelli di test xUnit di Gerard Meszaros: il codice del test di refactoring è pieno zeppo di schemi per il test dell'unità. So che stai cercando pattern su TDD, ma penso che troverai molto materiale utile in questo libro

Il libro è in safari in modo da poter dare una bella occhiata a cosa c'è dentro per vedere se potrebbe essere utile: http://my.safaribooksonline.com/9780131495050

2

hanno fatto capire che il passo successivo è capire come disaccoppiare codice per renderlo verificabile.

Cosa dovrei guardare per aiutarmi a fare questo? Esiste una serie specifica di schemi di progettazione che devo comprendere e iniziare a implementare e che consentirà test più semplici?

Proprio così! SOLID è quello che stai cercando (sì, davvero). Continuo a raccomandare questi 2 ebooks, specialmente quello su SOLID per il problema in questione.

Devi anche capire che è molto difficile se stai introducendo il test dell'unità su un codice esistente. Purtroppo il codice strettamente accoppiato è troppo comune. Questo non significa non farlo, ma per un buon momento sarà proprio come hai detto tu, i test saranno più concentrati in piccoli pezzi.

Nel corso del tempo queste dimensioni diventano più grandi, ma dipendono dalle dimensioni della base di codice esistente, dalle dimensioni della squadra e dal numero di persone che effettivamente la eseguono anziché aggiungere al problema.

0

Iniezione delle dipendenze/IoC. Leggi anche sui framework di injection dependance come SpringFramework e google-guice. Hanno anche come scrivere codice testabile.

1

Arrange, Act, Assert è un buon esempio di un modello che consente di strutturare il codice di prova intorno particolare casi d'uso.

Ecco un codice ipotetico C# che dimostra il modello.

[TestFixture] 
public class TestSomeUseCases() { 

    // Service we want to test 
    private TestableServiceImplementation service; 

    // IoC-injected mock of service that's needed for TestableServiceImplementation 
    private Mock<ISomeService> dependencyMock; 

    public void Arrange() { 
     // Create a mock of auxiliary service 
     dependencyMock = new Mock<ISomeService>(); 
     dependencyMock.Setup(s => s.GetFirstNumber(It.IsAny<int>)).Return(1); 

     // Create a tested service and inject the mock instance 
     service = new TestableServiceImplementation(dependencyMock.Object); 
    } 

    public void Act() { 
     service.ProcessFirstNumber(); 
    } 

    [SetUp] 
    public void Setup() { 
     Arrange(); 
     Act(); 
    } 

    [Test] 
    public void Assert_That_First_Number_Was_Processed() { 
     dependencyMock.Verify(d => d.GetFirstNumber(It.IsAny<int>()), Times.Exactly(1)); 
    } 
} 

Se si dispone di un sacco di scenari per testare, è possibile estrarre una classe astratta comune con cemento Disporre & bit Act (o solo Arrange) e implementare i bit astratti rimanenti & funzioni di test nelle classi ereditarie che gruppo funzioni di test.

Problemi correlati