unit-testing
Le regole generali per il test unitario per tutte le lingue
Ricerca…
introduzione
Quando si inizia con il test delle unità, vengono visualizzati tutti i tipi di domande:
Che cos'è il test unitario? Che cos'è un SetUp e TearDown? Come gestisco le dipendenze? Perché test delle unità? Come si fanno buoni test unitari?
Questo articolo risponderà a tutte queste domande, in modo che tu possa iniziare il test unitario in qualsiasi lingua tu voglia.
Osservazioni
Cos'è il test unitario?
Il test delle unità è la verifica del codice per garantire che esegua l'attività che è destinata a svolgere. Verifica il codice al livello più basso possibile - i singoli metodi delle tue classi.
Cos'è un'unità?
Qualsiasi modulo discreto di codice che può essere testato separatamente. La maggior parte delle classi temporali e dei loro metodi. Questa classe viene generalmente definita "Classe sotto test" (CUT) o "Sistema sotto test" (SUT)
La differenza tra test unitario e test di integrazione
Il test unitario è l'atto di testare una singola classe in isolamento, completamente al di fuori di qualsiasi sua effettiva dipendenza. Il test di integrazione è l'atto di testare una singola classe insieme a una o più delle sue dipendenze effettive.
SetUp e TearDown
Quando viene eseguito, il metodo SetUp viene eseguito prima di ogni test dell'unità e del TearDown dopo ogni test.
In generale, aggiungi tutti i passaggi prerequisiti nel SetUp e tutti i passaggi di pulizia in TearDown. Ma tu fai questo metodo solo se questi passaggi sono necessari per ogni test. In caso contrario, questi passaggi sono presi all'interno dei test specifici nella sezione "Organizza".
Come affrontare le dipendenze
Molte volte una classe ha la dipendenza di altre classi per eseguire i suoi metodi. Per essere in grado di non dipendere da queste altre classi, devi fingere queste. È possibile rendere queste lezioni da soli o utilizzare un framework di isolamento o mockup. Un framework di isolamento è una raccolta di codice che consente la facile creazione di classi false.
Lezioni false
Qualsiasi classe che fornisce funzionalità sufficienti per fingere che sia una dipendenza necessaria per un CUT. Esistono due tipi di falsi: Stub e Mock.
- Uno stub: un falso che non ha alcun effetto sul superamento o il fallimento del test e che esiste puramente per consentire l'esecuzione del test.
- Un finto: un falso che tiene traccia del comportamento del CUT e passa o fallisce il test in base a tale comportamento.
Perché test delle unità?
1. I test unitari troveranno bug
Quando scrivi una serie completa di test che definiscono quale sia il comportamento previsto per una determinata classe, viene rivelato tutto ciò che non si comporta come previsto.
2. I test unitari terranno gli insetti lontani
Apporta una modifica che introduce un bug e i tuoi test possono rivelarlo la prossima volta che esegui i test.
3. L'unità di test consente di risparmiare tempo
I test delle unità di scrittura garantiscono che il codice funzioni come progettato sin dall'inizio. I test unitari definiscono cosa dovrebbe fare il codice e quindi non passerai il tempo a scrivere codice che fa cose che non dovrebbe fare. Nessuno controlla il codice che non crede e deve fare qualcosa per farti pensare che funzioni. Spendi quel tempo per scrivere i test unitari.
4. I test unitari danno tranquillità
Puoi eseguire tutti questi test e sapere che il tuo codice funziona come dovrebbe. Conoscere lo stato del tuo codice, che funziona, e che puoi aggiornarlo e migliorarlo senza paura è una cosa molto buona.
5. I test unitari documentano l'uso corretto di una classe
I test unitari diventano semplici esempi di come funziona il codice, di cosa si prevede di fare e del modo corretto di utilizzare il codice da testare.
Regole generali per i test unitari
1. Per la struttura di un test unitario, seguire la regola AAA
Organizzare:
Installa cose da testare. Come variabili, campi e proprietà per consentire l'esecuzione del test e il risultato atteso.
Legge: in realtà chiama il metodo che stai testando
Affermare:
Chiama il framework di test per verificare che il risultato del tuo "atto" sia quello che ci si aspettava.
2. Provare una cosa alla volta in isolamento
Tutte le classi dovrebbero essere testate isolatamente. Non dovrebbero dipendere da nient'altro che i mock e gli stub. Non dovrebbero dipendere dai risultati di altri test.
3. Scrivi per prima cosa semplici test "al centro"
I primi test che scrivi dovrebbero essere i test più semplici. Dovrebbero essere quelli che illustrano sostanzialmente e facilmente la funzionalità che stai cercando di scrivere. Quindi, una volta superati questi test, dovresti iniziare a scrivere i test più complicati che testano i bordi e i limiti del tuo codice.
4. Scrivi test che testano i bordi
Una volta che le basi sono state testate e sai che la tua funzionalità di base funziona, dovresti testare i bordi. Una buona serie di test esplorerà i margini esterni di ciò che potrebbe accadere a un determinato metodo.
Per esempio:
- Cosa succede se si verifica un overflow?
- Cosa succede se i valori vanno a zero o al di sotto?
- Cosa succede se vanno su MaxInt o MinInt?
- Cosa succede se crei un arco di 361 gradi?
- Cosa succede se passi una stringa vuota?
- Cosa succede se una stringa ha una dimensione di 2 GB?
5. Test oltre i confini
I test unitari dovrebbero testare entrambi i lati di un determinato limite. Spostarsi oltre i confini sono luoghi in cui il codice potrebbe non funzionare o funzionare in modi imprevedibili.
6. Se puoi, prova l'intero spettro
Se è pratico, prova l'intero set di possibilità per la tua funzionalità. Se si tratta di un tipo enumerato, testare la funzionalità con ognuno degli elementi nell'enumerazione. Potrebbe essere poco pratico provare tutte le possibilità, ma se riesci a testare ogni possibilità, fallo.
7. Se possibile, coprire ogni percorso di codice
Anche questa è una sfida, ma se il tuo codice è progettato per i test e utilizzi uno strumento di copertura del codice, puoi assicurarti che ogni riga del tuo codice sia coperta da test di unità almeno una volta. Coprire ogni percorso di codice non garantisce che non ci siano bug, ma sicuramente ti fornisce preziose informazioni sullo stato di ogni riga di codice.
8. Scrivi test che rivelano un bug, quindi correggilo
Se trovi un bug, scrivi un test che lo rivela. Quindi, puoi correggere facilmente il bug eseguendo il debug del test. Quindi hai un bel test di regressione per assicurarti che se il bug ritorna per qualsiasi motivo, lo saprai subito. È davvero facile correggere un bug quando si esegue un test semplice e diretto nel debugger.
Un vantaggio collaterale qui è che hai testato il tuo test. Perché hai visto il test fallire e poi quando lo hai visto passare, sai che il test è valido in quanto è stato dimostrato che funziona correttamente. Questo lo rende un test di regressione ancora migliore.
9. Rendere ciascun test indipendente l'uno dall'altro
I test non dovrebbero mai dipendere l'uno dall'altro. Se i test devono essere eseguiti in un determinato ordine, è necessario modificare i test.
10. Scrivi un assert per test
Dovresti scrivere un assert per test. Se non riesci a farlo, rifatta il codice in modo che gli eventi SetUp e TearDown vengano utilizzati per creare correttamente l'ambiente in modo che ogni test possa essere eseguito individualmente.
11. Nomina chiaramente i tuoi test. Non aver paura dei nomi lunghi
Dato che stai facendo un assert per test, ogni test può diventare molto specifico. Pertanto, non aver paura di utilizzare nomi di test lunghi e completi.
Un nome completo lungo ti consente di sapere immediatamente quale test ha avuto esito negativo e esattamente ciò che il test stava tentando di eseguire.
Test lunghi e chiaramente denominati possono anche documentare i test. Un test denominato "DividedByZeroShouldThrowException" documenta esattamente ciò che fa il codice quando si tenta di dividere per zero.
12. Verifica che ogni eccezione sollevata sia effettivamente aumentata
Se il tuo codice solleva un'eccezione, allora scrivi un test per assicurarti che ogni eccezione generata aumenti di fatto quando è previsto.
13. Evitare l'uso di CheckTrue o Assert.IsTrue
Evita di verificare una condizione booleana. Ad esempio, se invece si controlla se due cose sono uguali a CheckTrue o Assert.IsTrue, utilizzare invece CheckEquals o Assert.IsEqual. Perché? A causa di ciò:
CheckTrue (Expected, Actual) Questo riporterà qualcosa del tipo: "Alcuni test falliscono: l'Expected era True ma il risultato effettivo era False."
Questo non ti dice nulla.
CheckEquals (atteso, effettivo)
Questo ti dirà qualcosa del tipo: "Alcuni test falliti: previsto 7 ma il risultato effettivo era 3."
Utilizzare solo CheckTrue o Assert.IsTrue quando il valore previsto è effettivamente una condizione booleana.
14. Esegui costantemente i tuoi test
Esegui i tuoi test mentre stai scrivendo il codice. I test dovrebbero essere eseguiti velocemente, permettendoti di eseguirli anche dopo modifiche minori. Se non è possibile eseguire i test come parte del normale processo di sviluppo, qualcosa non funziona. I test unitari dovrebbero funzionare quasi all'istante. Se non lo sono, probabilmente è perché non li stai facendo da soli.
15. Esegui i test come parte di ogni build automatizzata
Proprio come dovresti eseguire il test mentre sviluppi, dovrebbero anche essere parte integrante del tuo processo di integrazione continua. Un test fallito dovrebbe significare che la tua build è rotta. Non lasciare che i test in mancanza durino. Consideralo un errore di build e risolvilo immediatamente.
Esempio di test unitario semplice in C #
Per questo esempio testeremo il metodo di somma di un semplice calcolatore.
In questo esempio testeremo l'applicazione: ApplicationToTest. Questo ha una classe chiamata Calc. Questa classe ha un metodo Sum ().
Il metodo Sum () ha il seguente aspetto:
public void Sum(int a, int b)
{
return a + b;
}
Il test unitario per testare questo metodo è simile a questo:
[Testclass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
//Arrange
ApplicationToTest.Calc ClassCalc = new ApplicationToTest.Calc();
int expectedResult = 5;
//Act
int result = ClassCalc.Sum(2,3);
//Assert
Assert.AreEqual(expectedResult, result);
}
}