CSharp:Unit Test
From Aino Wiki
Contents
Definizione
Gli Unit Test aiutano a testare una funzione basica.
Dato un caso d'uso, lo Unit Test aiuta a verificare che la funzione dedicata allo scopo assolva alla sua missione.
In sostanza alla solution su cui si vuol applicare una o più batterie di unit test, si aggiungerà un progetto di tipo "Test"; ogni sua classe avrà dei metodi aggiunti per validare il funzionamento di una funzione di una classe già esistente (di uno dei progetti della solution iniziale).
Praticamente esistono dei Framework che aiutano nella stesura di UnitTest con una serie di funzionalità che consentono di testare specificatamente delle funzioni del nostro applicativo. Uno dei più semplici da installare ed usare è quello di Charlie Poole, Rob Prouse, Simone Busoli, Neil Colvin, "NUnit" (sito NUnit.org).
Preparazione
Framework:
- rhino mochks
Installazione NUnit
Supponendo di non avere disponibile questo framework nel nostro Visual Studio, lo si può installare usando NuGet.
(NUnit.org, Wiki).
Usando NuGet
Scegliere la libreria interessata:
Procedere all'installazione
Creazione del progetto di Test
Aggiungiamo un nuovo progetto di Test alla nostra solution:
Al progetto di test aggiungere una reference del progetto da Testare e Creare una prima classe di test.
Ecco un esempio di vari UnitTest per testare una funzionalità:
Esempi
Da TDD
Dopo una sessione di Test Driven Developping (TDD)
Il seguente codice (Capitoli: "Funzione da testare", "Classe con Unit tests") è stato scritto in sede di una sessione di traning all'Extreme programming (XP), è una esercitazione allo sviluppo TDD, sviluppo guidato dal testing.
L'esercizio consiste nell'implementazione di una funzionalità (mi riferisco al metodo "StringSum(string strCSVInput)
" del codice seguente) come risultato di diverse iterazioni ognuna delle quali aggiunge una caratteristica a quanto implementato nell'iterazione precedente.
Ogni iterazione è etichettata con: TDD n. Una iterazione consiste nello sviluppare un test che risponda all'implementazione di una funzionalità che via via sarà incrementa dalle iterazioni successive.
Quindi se dobbiamo sviluppare una funzione, es:
public static int StringSum(string strCSVInput) { return -1; }
Indipendentemente da quanto dovremmo scrivere come implementazione della funzione "StringSum" inizieremo prima con l'implementazione del Test1 eticchettato "TDD 1".
Poichè non abbiamo ancora scritto una riga di codice della funzione da testare, la prima volta il primo test fallirà banalmente (restituisce -1 e dovrebbe restituire 0). Questo è un primo step della TDD.
Poiché il test fallisce, adatteremo (al primo passo invece scriveremo codice da zero) il codice perché il test eseguito dopo sia passato positivamente.
Funzione da testare
Attenzione il seguente codice è il risultato dell'implementazione dei Test riportati al capitolo successivo: "Classe con Unit tests".
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace StringCalculator { public static class StringCalculator { public static int StringSum(string strCSVInput) { int outputNr = 0; // Refactoring //// TDD 1 //if (string.IsNullOrWhiteSpace(strCSVInput)) //{ // return 0; //} //else if (!string.IsNullOrWhiteSpace(strCSVInput)) { int partialNumberAcquired = 0; string errorNotNegativeNumber = string.Empty; //TDD 10 char customDelimiter = ','; // TDD 8 string patternCustomDelimiter = "//["; if (strCSVInput.IndexOf(patternCustomDelimiter) >= 0) { string[] arrPattern = strCSVInput.Split('\n'); strCSVInput = strCSVInput.Replace(arrPattern[0] + "\n", string.Empty); string paternDelimier = arrPattern[0] .Replace(patternCustomDelimiter, string.Empty) .Replace("]", string.Empty); customDelimiter = paternDelimier[0]; } strCSVInput = strCSVInput.Replace('\n', customDelimiter);// TDD 7 string[] arrString = strCSVInput.Split(customDelimiter); foreach (string strNumber in arrString) { partialNumberAcquired = int.Parse(strNumber); if (partialNumberAcquired < 0) // TDD 9 { // TDD 10 throw new NegativesNotAllowed("Negative number not allowed"); errorNotNegativeNumber += string.Format("{0} ", partialNumberAcquired); } outputNr += partialNumberAcquired; } if (!string.IsNullOrWhiteSpace(errorNotNegativeNumber)) { errorNotNegativeNumber = string.Format("Negative number '{0}' not expected", errorNotNegativeNumber.Trim()); throw new NegativesNotAllowed(errorNotNegativeNumber); } } return outputNr; // Refactoring // TDD 2 //return 5; // Refactor } } } // TDD 9 public class NegativesNotAllowed : Exception { public NegativesNotAllowed(string message) : base (message) { } }
Classe con Unit tests
I metodi in questa classe son stati, in realtà, implementati prima della stessa funzione da testare (StringSum(...)
). Ogni metodo risponde alle richieste di una iterazione, il metodo di test si scrive per verificare quel che si dovrà implementare e questa richiesta è enunciata da un caso d'uso.
Ad es all'iterazione corrispondente al TDD 1, vogliamo verificare che passando una stringa vuota alla funzione "StringSum
", si ottenga in output il numero 0 altrimenti il Test dovrà fallire.
L'istruzione:
Assert.AreEqual(outSum, 0);
assolve a questo scopo.
Segue il codice completo con la classe e tutti i metodi, uno per ogni iterazione TDD.
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; //using StringCalculator; namespace UnitTestProject1 { [TestClass] public class UnitTest1 { /// <summary> /// TDD 1 /// Verifica del comportamento di StringSum(...) in presenza di una stringa vuota /// </summary> [TestMethod] public void TestEmptyString() { string strToTest = string.Empty; int outSum = StringCalculator.StringCalculator.StringSum(strToTest); Assert.AreEqual(outSum, 0); } /// <summary> /// TDD 2 /// </summary> [TestMethod] public void TestOneNumber() { string strToTest = "5"; int outSum = StringCalculator.StringCalculator.StringSum(strToTest); Assert.AreEqual(outSum, 5); } /// <summary> /// TDD 3 /// </summary> [TestMethod] public void TestOneNumberDifferent() { string strToTest = "7"; int outSum = StringCalculator.StringCalculator.StringSum(strToTest); Assert.AreEqual(outSum, 7); } /// <summary> /// TDD 4 /// </summary> [TestMethod] public void TestTwoNumber() { string strToTest = "2,3"; int outSum = StringCalculator.StringCalculator.StringSum(strToTest); Assert.AreEqual(outSum, 5); } /// <summary> /// TDD 5 /// </summary> [TestMethod] public void TestTreeNumber() { string strToTest = "2,3,5"; int outSum = StringCalculator.StringCalculator.StringSum(strToTest); Assert.AreEqual(outSum, 10); } /// <summary> /// TDD 6 /// </summary> [TestMethod] public void TestDifferentCSVNumber_newLine() { string strToTest = "2\n3\n5"; int outSum = StringCalculator.StringCalculator.StringSum(strToTest); Assert.AreEqual(outSum, 10); } /// <summary> /// TDD 7 /// </summary> [TestMethod] public void TestDifferentCSVNumber_CSV() { string strToTest = "2\n3,5"; int outSum = StringCalculator.StringCalculator.StringSum(strToTest); Assert.AreEqual(outSum, 10); } /// <summary> /// TDD 8 /// </summary> [TestMethod] public void TestDifferentCSVNumber_CustomCSV() { string strToTest = "//[;]\n1;2;3"; int outSum = StringCalculator.StringCalculator.StringSum(strToTest); Assert.AreEqual(outSum, 6); } /// <summary> /// TDD 9 /// </summary> [TestMethod] public void TestDifferentCSVNumber_NoNegative() { try { string strToTest = "1,-2,3,4,5"; int outSum = StringCalculator.StringCalculator.StringSum(strToTest); } catch (NegativesNotAllowed ex) { return; // Ok as expected } Assert.Fail("Exception not thrown"); } /// <summary> /// TDD 10 /// </summary> [TestMethod] public void TestDifferentCSVNumber_NoNegativeWithMessage() { try { string strToTest = "1,-2,3,4,-5"; int outSum = StringCalculator.StringCalculator.StringSum(strToTest); } catch (NegativesNotAllowed ex) { if (ex.Message != string.Format("Negative number '{0}' not expected", "-2 -5")) { Assert.Fail("Thrown message not expected"); } else { return; } } Assert.Fail("Exception not thrown"); } } }
Esecuzione dei test
Dopo aver implementato un singolo UnitTest o alla fune di tutte le iterazioni è sufficiente eseguirli premendo la combinazione di tasti: CTRL + R, A oppure:
Segue il risultato d'esecuzione di tutti i test, è un test negativo ovvero la funzionalità soddisfa tutte le richieste dei test, altrimenti anziché avere tutti "Passed" avremmo qualche "Fail".
Mappa e Link
Visual Studio | Programmazione
Parole chiave: