Domanda

Sono sicuro che la maggior parte di voi sta scrivendo molti test automatizzati e che si è anche imbattuto in alcune insidie ​​​​comuni durante i test unitari.

La mia domanda è: seguite qualche regola di condotta per la scrittura dei test in modo da evitare problemi in futuro?Per essere più specifici:Quali sono le proprietà di buoni test unitari o come scrivi i tuoi test?

Sono incoraggiati suggerimenti indipendenti dalla lingua.

È stato utile?

Soluzione

Vorrei iniziare collegando le fonti: Unit test pragmatico in Java con JUnit (Esiste anche una versione con C#-Nunit..ma ho questo..è per la maggior parte agnostico.Consigliato.)

Dovrebbero essere buoni test UN VIAGGIO (L'acronimo non è abbastanza appiccicoso: ho una stampa del foglietto illustrativo nel libro che ho dovuto estrarre per assicurarmi di aver capito bene..)

  • Automatico :Il richiamo dei test e il controllo dei risultati per PASS/FAIL dovrebbero essere automatici
  • Completo:Copertura;Sebbene i bug tendano a raggrupparsi in determinate aree del codice, assicurati di testare tutti i percorsi e gli scenari chiave.Utilizza gli strumenti se devi conoscere regioni non testate
  • Ripetibile:I test dovrebbero produrre ogni volta gli stessi risultati.ogni volta.I test non dovrebbero fare affidamento su parametri incontrollabili.
  • Indipendente:Molto importante.
    • I test dovrebbero prova solo una cosa Al tempo.Asserzioni multiple vanno bene fintanto che testano tutte una caratteristica/comportamento.Quando un test fallisce, dovrebbe individuare la posizione del problema.
    • Test non dovrebbero fare affidamento l'uno sull'altro - Isolato.Nessuna ipotesi sull'ordine di esecuzione del test.Assicurati di "fare tabula rasa" prima di ogni test utilizzando la configurazione/smontaggio in modo appropriato
  • Professionale:A lungo termine avrai tanto codice di test quanto la produzione (se non di più), quindi segui lo stesso standard di buona progettazione per il tuo codice di test.Classi di metodi ben fattorizzati con nomi rivelatori di intenzioni, nessuna duplicazione, test con nomi validi, ecc.

  • Vengono eseguiti anche buoni test Veloce.qualsiasi test che richieda più di mezzo secondo per essere eseguito..bisogna lavorarci su.Più tempo impiega la suite di test per l'esecuzione.meno frequentemente verrà eseguito.Più modifiche lo sviluppatore cercherà di introdurre di nascosto tra una corsa e l'altra.se si rompe qualcosa..ci vorrà più tempo per capire quale cambiamento sia stato il colpevole.

Aggiornamento 2010-08:

  • Leggibile :Questo può essere considerato parte del Professional, tuttavia non lo sottolineeremo mai abbastanza.Un test del fuoco sarebbe quello di trovare qualcuno che non fa parte della tua squadra e chiedergli di capire il comportamento sotto test entro un paio di minuti.I test devono essere mantenuti proprio come il codice di produzione, quindi rendili facili da leggere anche se richiede uno sforzo maggiore.I test dovrebbero essere simmetrici (seguire uno schema) e concisi (testare un comportamento alla volta).Utilizzare una convenzione di denominazione coerente (ad es.lo stile TestDox).Evitare di ingombrare il test con "dettagli accessori".diventare un minimalista.

Oltre a queste, la maggior parte delle altre sono linee guida che riducono il lavoro a basso beneficio:per esempio."Non testare il codice che non possiedi" (ad es.DLL di terze parti).Non testare getter e setter.Tieni d'occhio il rapporto costi-benefici o la probabilità di difetto.

Altri suggerimenti

  1. Non scrivere test enormi. Come suggerisce l'"unità" in "test unitario", crea ciascuno come atomico E isolato possibile.Se necessario, crea precondizioni utilizzando oggetti fittizi, anziché ricreare manualmente una parte eccessiva del tipico ambiente utente.
  2. Non testare cose che ovviamente funzionano. Evita di testare le classi di un fornitore di terze parti, in particolare quello che fornisce le API principali del framework in cui codifichi.Ad esempio, non provare ad aggiungere un elemento alla classe Hashtable del fornitore.
  3. Prendi in considerazione l'utilizzo di uno strumento di copertura del codice come NCover per aiutarti a scoprire casi limite che devi ancora testare.
  4. Prova a scrivere il test Prima l'implemento. Pensa al test più come a una specifica a cui aderirà la tua implementazione.Cfr.anche lo sviluppo guidato dal comportamento, un ramo più specifico dello sviluppo guidato dai test.
  5. Sii coerente. Se scrivi solo test per parte del tuo codice, è poco utile.Se lavori in gruppo e alcuni o tutti gli altri non scrivono test, non è neanche molto utile.Convinci te stesso e tutti gli altri dell'importanza (e che fa risparmiare tempo proprietà) di test o non preoccuparti.

La maggior parte delle risposte qui sembrano riguardare le migliori pratiche di test unitario in generale (quando, dove, perché e cosa), piuttosto che scrivere effettivamente i test stessi (come).Dato che la domanda sembrava piuttosto specifica sulla parte "come", ho pensato di pubblicare questo, tratto da una presentazione "borsa marrone" che ho condotto presso la mia azienda.

Le 5 leggi di Womp per scrivere i test:


1.Utilizzare nomi di metodi di prova lunghi e descrittivi.

   - Map_DefaultConstructorShouldCreateEmptyGisMap()
   - ShouldAlwaysDelegateXMLCorrectlyToTheCustomHandlers()
   - Dog_Object_Should_Eat_Homework_Object_When_Hungry()

2.Scrivi i tuoi test in un file Stile Organizza/Agisci/Afferma.

  • Mentre questa strategia organizzativa è in circolazione da un po 'di tempo e ha chiamato molte cose, l'introduzione dell'acronimo "AAA" recentemente è stata un ottimo modo per farcela.Rendere tutti i test coerenti con lo stile AAA li rende facili da leggere e mantenere.

3.Fornisci sempre un messaggio di errore con le tue asserzioni.

Assert.That(x == 2 && y == 2, "An incorrect number of begin/end element 
processing events was raised by the XElementSerializer");
  • Una pratica semplice ma gratificante che rende evidente nella tua applicazione runner ciò che non ha funzionato.Se non fornisci un messaggio, di solito otterrai qualcosa come "Previsto vero, era falso" nell'output dell'errore, il che ti costringe a leggere il test per scoprire cosa c'è che non va.

4.Commenta il motivo del test – qual è il presupposto aziendale?

  /// A layer cannot be constructed with a null gisLayer, as every function 
  /// in the Layer class assumes that a valid gisLayer is present.
  [Test]
  public void ShouldNotAllowConstructionWithANullGisLayer()
  {
  }
  • Questo può sembrare ovvio, ma questa pratica proteggerà l'integrità dei tuoi test da persone che non capiscono il motivo dietro il test in primo luogo.Ho visto molti test essere rimossi o modificati che erano perfettamente bene, semplicemente perché la persona non ha capito le ipotesi che il test stava verificando.
  • Se il test è banale o il nome del metodo è sufficientemente descrittivo, può essere consentito lasciare il commento.

5.Ogni test deve sempre ripristinare lo stato di qualsiasi risorsa che tocca

  • Usa i mock ove possibile per evitare di affrontare le risorse reali.
  • La pulizia deve essere eseguita a livello di prova.I test non devono fare affidamento sull'ordine di esecuzione.

Tieni a mente questi obiettivi (adattato dal libro xUnit Test Patterns di Meszaros)

  • I test dovrebbero ridurre il rischio, non introdurlo.
  • I test dovrebbero essere facili da eseguire.
  • I test dovrebbero essere facili da mantenere man mano che il sistema si evolve attorno a loro

Alcune cose per renderlo più semplice:

  • I test dovrebbero fallire solo a causa di un motivo.
  • I test dovrebbero testare solo una cosa
  • Ridurre al minimo le dipendenze dei test (nessuna dipendenza da database, file, interfaccia utente ecc.)

Non dimenticare che puoi eseguire test di integrazione anche con il tuo framework xUnit ma mantieni separati i test di integrazione e i test unitari

I test dovrebbero essere isolati.Un test non dovrebbe dipendere da un altro.Inoltre, un test non dovrebbe fare affidamento su sistemi esterni.In altre parole, prova tuo codice, non il codice da cui dipende il codice. Puoi testare tali interazioni come parte dell'integrazione o dei test funzionali.

Alcune proprietà dei grandi test unitari:

  • Quando un test fallisce, dovrebbe essere immediatamente evidente dove si trova il problema.Se devi utilizzare il debugger per individuare il problema, i test non sono sufficientemente granulari.Avere esattamente un'affermazione per test aiuta qui.

  • Quando si esegue il refactoring, nessun test dovrebbe fallire.

  • I test dovrebbero essere eseguiti così velocemente da non esitare mai a eseguirli.

  • Tutti i test dovrebbero essere sempre superati;nessun risultato non deterministico.

  • I test unitari dovrebbero essere ben strutturati, proprio come il codice di produzione.

@Alotor:Se stai suggerendo che una libreria dovrebbe avere solo unit test sulla sua API esterna, non sono d'accordo.Voglio test unitari per ogni classe, comprese le classi che non espongo a chiamanti esterni.(Tuttavia, se sento il bisogno di scrivere test per metodi privati, allora devo effettuare il refactoring.)


MODIFICARE:C'era un commento sulla duplicazione causata da "un'asserzione per test".Nello specifico, se si dispone di codice per impostare uno scenario e quindi si desidera fare più asserzioni al riguardo, ma si dispone di una sola asserzione per test, è possibile duplicare l'impostazione su più test.

Non adotto questo approccio.Invece, utilizzo dispositivi di prova per scenario.Ecco un esempio approssimativo:

[TestFixture]
public class StackTests
{
    [TestFixture]
    public class EmptyTests
    {
        Stack<int> _stack;

        [TestSetup]
        public void TestSetup()
        {
            _stack = new Stack<int>();
        }

        [TestMethod]
        [ExpectedException (typeof(Exception))]
        public void PopFails()
        {
            _stack.Pop();
        }

        [TestMethod]
        public void IsEmpty()
        {
            Assert(_stack.IsEmpty());
        }
    }

    [TestFixture]
    public class PushedOneTests
    {
        Stack<int> _stack;

        [TestSetup]
        public void TestSetup()
        {
            _stack = new Stack<int>();
            _stack.Push(7);
        }

        // Tests for one item on the stack...
    }
}

Quello che cerchi è la delineazione dei comportamenti della classe sotto test.

  1. Verifica dei comportamenti attesi.
  2. Verifica dei casi di errore.
  3. Copertura di tutti i percorsi del codice all'interno della classe.
  4. Esercitare tutte le funzioni membro della classe.

L'intento fondamentale è aumentare la tua fiducia nel comportamento della classe.

Ciò è particolarmente utile quando si esamina il refactoring del codice.Martin Fowler ha un argomento interessante articolo per quanto riguarda i test sul suo sito web.

HTH.

saluti,

rapinare

Il test inizialmente dovrebbe fallire.Poi dovresti scrivere il codice che li fa passare, altrimenti corri il rischio di scrivere un test che è buggato e passa sempre.

Mi piace l'acronimo Right BICEP da quanto sopra Test unitari pragmatici libro:

  • Giusto:Sono i risultati Giusto?
  • B:Sono tutti i Ble condizioni generali sono corrette?
  • IO:Possiamo controllare? iorelazioni inverse?
  • C:Possiamo Ccontrollare i risultati utilizzando altri mezzi?
  • E:Possiamo forzare? esi verificano condizioni errate?
  • P:Sono Pcaratteristiche prestazionali entro i limiti?

Personalmente ritengo che si possa andare molto lontano controllando di ottenere i risultati giusti (1+1 dovrebbe restituire 2 in una funzione di addizione), provando tutte le condizioni al contorno che ti vengono in mente (come usare due numeri la cui somma è maggiore del valore intero massimo nella funzione di aggiunta) e forzare condizioni di errore come guasti di rete.

I buoni test devono essere mantenibili.

Non ho ancora capito come farlo per ambienti complessi.

Tutti i libri di testo iniziano a non essere incollati quando la base del codice inizia a raggiungere le centinaia di 1000 o milioni di righe di codice.

  • Le interazioni di squadra esplodono
  • numero di casi di test esplodono
  • le interazioni tra i componenti esplodono.
  • il tempo necessario per creare tutti gli unittest diventa una parte significativa del tempo di costruzione
  • una modifica API può estendersi a centinaia di casi di test.Anche se il cambio del codice di produzione è stato facile.
  • aumenta il numero di eventi richiesti per sequenziare i processi nello stato corretto, il che a sua volta aumenta il tempo di esecuzione del test.

Una buona architettura può controllare parte dell'esplosione dell'interazione, ma inevitabilmente man mano che i sistemi diventano più complessi, il sistema di test automatizzato cresce con esso.

È qui che inizi a dover affrontare i compromessi:

  • testare solo l'API esterna, altrimenti il ​​refactoring degli interni comporta una significativa rielaborazione del test case.
  • la configurazione e lo smontaggio di ogni test diventano più complicati poiché un sottosistema incapsulato conserva più stato.
  • la compilazione notturna e l'esecuzione automatizzata dei test richiedono ore.
  • l'aumento dei tempi di compilazione ed esecuzione significa che i progettisti non eseguono o non eseguiranno tutti i test
  • Per ridurre i tempi di esecuzione dei test, considera la possibilità di sequenziare i test per ridurre la configurazione e lo smontaggio

Devi anche decidere:

dove memorizzi i casi di test nella tua codebase?

  • come documenti i tuoi casi di test?
  • è possibile riutilizzare le apparecchiature di test per risparmiare sulla manutenzione del test case?
  • cosa succede quando l'esecuzione di un test case notturno fallisce?Chi fa il triage?
  • Come mantieni gli oggetti finti?Se disponi di 20 moduli che utilizzano tutti la propria versione di un'API di registrazione simulata, la modifica dell'API si ripercuoterà rapidamente.Non cambiano solo i casi di test, ma cambiano anche i 20 oggetti simulati.Questi 20 moduli sono stati scritti nel corso di diversi anni da molti team diversi.È un classico problema di riutilizzo.
  • gli individui e i loro team comprendono il valore dei test automatizzati, ma non gli piace il modo in cui li stanno eseguendo gli altri team.:-)

Potrei continuare all'infinito, ma il punto è che:

I test devono essere mantenibili.

Ho trattato questi principi qualche tempo fa Questo articolo di MSDN Magazine che penso sia importante leggere per qualsiasi sviluppatore.

Il modo in cui definisco gli unit test "buoni" è se possiedono le seguenti tre proprietà:

  • Sono leggibili (denominazione, asserzioni, variabili, lunghezza, complessità...)
  • Sono manutenibili (nessuna logica, non eccessivamente specificati, basati sullo stato, rifattorizzati...)
  • Sono affidabili (testare la cosa giusta, isolata, non test di integrazione...)
  • Il test unitario testa solo l'API esterna della tua unità, non dovresti testare il comportamento interno.
  • Ogni test di un TestCase dovrebbe testare un (e solo uno) metodo all'interno di questa API.
    • Dovrebbero essere inclusi casi di test aggiuntivi per i casi di fallimento.
  • Verifica la copertura dei tuoi test:Una volta testata un'unità, il 100% delle linee all'interno di questa unità dovrebbero essere state eseguite.

Jay Fields ha una tanti buoni consigli sulla scrittura di test unitari e c'è un post in cui riassume i consigli più importanti.Lì leggerai che dovresti pensare in modo critico al tuo contesto e giudicare se il consiglio vale per te.Qui ottieni tantissime risposte straordinarie, ma sta a te decidere quale è la migliore per il tuo contesto.Provali e fai il refactoring se ti sembra cattivo.

Cordiali saluti

Non dare mai per scontato che un banale metodo a 2 righe funzionerà.Scrivere un test unitario rapido è l'unico modo per evitare che il test nullo mancante, il segno meno fuori posto e/o un sottile errore di ambito ti colpiscano, inevitabilmente quando hai ancora meno tempo per affrontarlo di adesso.

Condivido la risposta "UN VIAGGIO", tranne quello i test DOVREBBERO fare affidamento l'uno sull'altro!!!

Perché?

DRY - Non ripeterti - vale anche per i test!Le dipendenze dei test possono aiutare a 1) risparmiare tempo di configurazione, 2) risparmiare risorse per le apparecchiature e 3) individuare i guasti.Naturalmente, solo a condizione che il tuo framework di test supporti dipendenze di prima classe.Altrimenti, lo ammetto, sono pessimi.

Seguito http://www.iam.unibe.ch/~scg/Research/JExample/

Spesso i test unitari si basano su oggetti fittizi o dati fittizi.Mi piace scrivere tre tipi di test unitari:

  • unit test "transitori":creano i propri oggetti/dati fittizi e testano la loro funzione con essi, ma distruggono tutto e non lasciano traccia (come nessun dato in un database di test)
  • test unitario "persistente":testano le funzioni all'interno del codice creando oggetti/dati che saranno necessari in seguito a funzioni più avanzate per il proprio test unitario (evitando che quelle funzioni avanzate ricreano ogni volta il proprio set di oggetti/dati fittizi)
  • test unitari "basati su persistenti":unit test utilizzando oggetti/dati fittizi già presenti (perché creati in un'altra sessione di unit test) dagli unit test persistenti.

Il punto è evitare di rigiocare qualunque cosa per poter testare tutte le funzioni.

  • Eseguo il terzo tipo molto spesso perché tutti gli oggetti/dati fittizi sono già presenti.
  • Eseguo il secondo tipo ogni volta che il mio modello cambia.
  • Eseguo il primo per controllare le funzioni di base di tanto in tanto, per verificare le regressioni di base.

Pensa ai 2 tipi di test e trattali in modo diverso: test funzionali e test delle prestazioni.

Utilizza input e metriche diversi per ciascuno.Potrebbe essere necessario utilizzare software diversi per ciascun tipo di test.

Utilizzo una convenzione di denominazione dei test coerente descritta da Standard di denominazione dei test unitari di Roy Osherove Ogni metodo in una determinata classe del test case ha il seguente stile di denominazione MethodUnderTest_Scenario_ExpectedResult.

    La prima sezione del nome del test è il nome del metodo nel sistema sottoposto a test.
    Il prossimo è lo scenario specifico che viene testato.
    Finalmente ci sono i risultati di quello scenario.

Ogni sezione utilizza Upper Camel Case ed è delimitata da un segno di sottolineatura.

L'ho trovato utile quando eseguo il test, i test vengono raggruppati in base al nome del metodo da testare.E avere una convenzione consente ad altri sviluppatori di comprendere l'intento del test.

Aggiungo anche parametri al nome del metodo se il metodo sotto test è stato sovraccaricato.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top