Login Login
MORE

WIDGETS

Widgets

Wanted articles
Who is online?
Article tools

CSharp:Unit Test

From Aino Wiki

Jump to: navigation, search

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:

  1. 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

NuGet instalazione fwk NUnit.png

Scegliere la libreria interessata:

NuGet instalazione fwk NUnit 02.png

Procedere all'installazione

NuGet instalazione fwk NUnit 03.png

Creazione del progetto di Test

Aggiungiamo un nuovo progetto di Test alla nostra solution:

UnitTest New Project.png

Al progetto di test aggiungere una reference del progetto da Testare e Creare una prima classe di test.

UnitTest New Project 02.png

Ecco un esempio di vari UnitTest per testare una funzionalità:

UnitTest New Project 03.png

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:

TDD UnitTest Exec 01.png

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".

TDD UnitTest Exec 02.png

Mappa e Link


C# | Testing


Visual Studio | Programmazione


Parole chiave:

Author