Test unitario in breve Visual Studio

Questo è un estratto dall'eBook Testing Unit Affinity, di Marc Clifton, gentilmente fornito da Syncfusion.

Un test unitario è composto da due cose:

  • Una classe che rappresenta il dispositivo di prova.
  • Metodi nella classe che rappresenta i test unitari.

Visual Studio creerà automaticamente uno stub per un progetto di test, da cui inizieremo.

Creazione di un progetto di test unitario in Visual Studio

I test unitari vengono generalmente collocati in un progetto separato (risultante in un assembly distinto) dal codice dell'applicazione. In Visual Studio 2008 o 2012, è possibile creare un progetto di test unitario facendo clic con il tasto destro sulla soluzione e selezionando Inserisci seguito da Nuovo progetto dal menu a comparsa:

Aggiungere un nuovo progetto

Dalla finestra di dialogo che appare, seleziona a Test progetto:

VS2008 Nuovo progetto di test VS2012 Nuovo progetto di test

Visual Studio 2008 creerà un file stub, "UnitTest1.cs" (se hai selezionato il linguaggio C #), con una serie di utili commenti nello stub. Visual Studio 2012 crea uno stub molto più tergente:

usando il sistema; utilizzando Microsoft.VisualStudio.TestTools.UnitTesting; namespace VS2012UnitTestProject1 [TestClass] publicTest UnitTest1 [TestMethod] public void TestMethod1 ()  

Per gli utenti di Visual Studio 2008

Visual Studio 2008 creerà anche una classe TestContext, che non esiste più in VS2012 e la ignoreremo, utilizzare invece lo stub precedente da VS2012.

Inoltre, elimina il file "ManualTest1.mht", altrimenti ti verrà richiesto di selezionare i risultati del test e inserire le note di test manualmente.

Partite di prova

Si noti che la classe è decorata con l'attributo TestClass. Questo definisce il dispositivo di prova, una raccolta di metodi di prova.

Metodi di prova

Si noti che il metodo è decorato con l'attributo TestMethod. Questo definisce un metodo, che verrà eseguito dal dispositivo di prova.


La classe Assert

La classe assert definisce i seguenti metodi statici che possono essere utilizzati per verificare il calcolo di un metodo:

  • AreEqual / AreNotEqual
  • AreSame / AreNotSame
  • IsTrue / IsFalse
  • IsNull / IsNotNull
  • IsInstanceOfType / IsNotInstanceOfType

Molte di queste asserzioni sono sovraccariche e si consiglia di rivedere l'intera documentazione fornita da Microsoft.

Fondamenti di fare un'affermazione

Notare che i seguenti esempi utilizzano VS2008.

Le asserzioni sono la spina dorsale di ogni test. Ci sono una varietà di asserzioni che si possono fare riguardo ai risultati di un test. Per cominciare, scriveremo una semplice asserzione che afferma "uno uguale a uno", in altre parole, un truismo:

[TestClass] publicTest UnitTest1 [TestMethod] public void TestMethod1 () Assert.IsTrue (1 == 1);  

Esegui il test, che dovrebbe risultare in "Passato":

Asserzione semplice

AreEqual / AreNotEqual

I metodi AreEqual e AreNotEqual confrontano:

  • oggetti
  • doppio
  • single
  • stringhe
  • dati digitati

Essi assumono la forma di confrontare il previsto (il primo parametro) con il valore effettivo (il secondo parametro). Per quanto riguarda i valori singoli e doppi, è possibile specificare "entro una certa precisione". Infine, tutti i sovraccarichi hanno l'opzione di visualizzare un messaggio (facoltativamente formattato) se l'asserzione fallisce.

Per quanto riguarda l'uguaglianza degli oggetti, questo metodo confronta se le istanze sono identiche:

public class AnObject  [TestMethod] public void ObjectEqualityTest () AnObject object1 = new AnObject (); AnObject object2 = new AnObject (); Assert.AreNotEqual (oggetto1, oggetto2);  

Il test precedente passa, poiché object1 e object2 non sono uguali. Tuttavia, se la classe esegue l'override del metodo Equals, l'uguaglianza si basa sul confronto effettuato con il metodo Equals implementato nella classe. Per esempio:

public class AnObject public int SomeValue get; impostato;  public override bool Equals (object obj) return SomeValue == ((AnObject) obj) .SomeValue;  [TestMethod] public void ObjectEqualityTest () AnObject object1 = new AnObject () SomeValue = 1; AnObject object2 = new AnObject () SomeValue = 1; Assert.AreEqual (oggetto1, oggetto2);  

AreSame / AreNotSame

Questi due metodi verificano che il casi sono uguali (o meno). Per esempio:

[TestMethod] public void SamenessTest () AnObject object1 = new AnObject () SomeValue = 1; AnObject object2 = new AnObject () SomeValue = 1; Assert.AreNotSame (oggetto1, oggetto2);  

Anche se la classe AnObject sovrascrive l'operatore Equals, il test precedente passa perché il casi dei due oggetti non sono la stessa cosa.

IsTrue / IsFalse

Questi due metodi ti consentono di verificare la verità di un confronto di valori. Dal punto di vista della leggibilità, vengono generalmente utilizzati i metodi IsTrue e IsFalse valore confronti, mentre AreEqual e AreSame sono in genere utilizzati per il confronto casi (oggetti).

Per esempio:

[TestMethod] public void IsTrueTest () AnObject object1 = new AnObject () SomeValue = 1; Assert.IsTrue (object1.SomeValue == 1);  

Questo verifica il valore di una proprietà.

IsNull / IsNotNull

Questi due test verificano se un oggetto è nullo o no:

[TestMethod] public void IsNullTest () AnObject object1 = null; Assert.IsNull (object1);  

IsInstanceOfType / IsNotInstanceOfType

Questi due metodi verificano che un oggetto sia un'istanza di un tipo specifico (o meno). Per esempio:

public class AnotherObject  [TestMethod] public void TypeOfTest () AnObject object1 = new AnObject (); Assert.IsNotInstanceOfType (object1, typeof (AnotherObject));  

inconcludente

Il metodo Assert.Inconclusive può essere utilizzato per specificare che il test o la funzionalità del test non è ancora stato implementato e quindi il test non è conclusivo.

Cosa succede quando un'asserzione fallisce?

Per quanto riguarda il testing delle unità di Visual Studio, quando un'asserzione fallisce, il metodo Assert lancia un'AssertFailedException. Questa eccezione non dovrebbe mai essere gestita dal tuo codice di test.


Altre classi di asserzioni

Ci sono altre due classi di asserzione:

  • CollectionAssert
  • StringAssert

Come indicano i loro nomi, queste asserzioni operano su collezioni e stringhe, rispettivamente.

Asserzioni di raccolta

Questi metodi sono implementati nella classe Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert. Si noti che il parametro di raccolta in questi metodi prevede che la raccolta implementa ICollection (confrontarlo con NUnit, che si aspetta IEnumerable).

AllItemsAreInstanceOfType

Questa asserzione verifica che gli oggetti in una raccolta siano dello stesso tipo, che include i tipi derivati ​​del tipo atteso, come illustrato qui:

public class A  public class B: A  [TestClass] CollectionTests public class [TestMethod] public void InstancesOfTypeTest () List points = new List () new Point (1, 2), new Point (3, 4 ); Elenco items = new List() nuovo B (), nuovo A (); CollectionAssert.AllItemsAreInstancesOfType (points, typeof (Point)); CollectionAssert.AllItemsAreInstancesOfType (items, typeof (A));   

AllItemsAreNotNull

Questa asserzione verifica che gli oggetti nella raccolta non siano nulli.

AllItemsAreUnique

Questo test assicura che gli oggetti di una collezione siano unici. Se si confrontano le strutture:

[TestMethod] public void AreUniqueTest () List points = new List () new Point (1, 2), new Point (1, 2); CollectionAssert.AllItemsAreUnique (punti);  

le strutture sono confrontate per valore, non per istanza, il test precedente fallisce. Tuttavia, anche se la classe esegue l'override del metodo Equals:

public class AnObject public int SomeValue get; impostato;  public override bool Equals (object obj) return SomeValue == ((AnObject) obj) .SomeValue;  

questo test passa:

[TestMethod] public void AreUniqueObjectsTest () List items = new List() new AnObject () SomeValue = 1, new AnObject () SomeValue = 1; CollectionAssert.AllItemsAreUnique (voci);   

AreEqual / AreNotEqual

Questi test affermano che due collezioni sono uguali. I metodi includono sovraccarichi che consentono di fornire un metodo di confronto. Se un oggetto sostituisce il metodo Equals, tale metodo verrà utilizzato per determinare l'uguaglianza. Per esempio:

[TestMethod] public void AreEqualTest () List itemList1 = nuova lista() new AnObject () SomeValue = 1, new AnObject () SomeValue = 2; Elenco itemList2 = nuova lista() new AnObject () SomeValue = 1, new AnObject () SomeValue = 2; CollectionAssert.AreEqual (itemList1, itemList2);   

Queste due raccolte sono uguali perché la classe AnObject sovrascrive il metodo Equals (vedi esempio precedente).

Si noti che per passare l'asserzione, le liste devono essere della stessa lunghezza e sono considerate non uguali se le liste sono identiche tranne che in un ordine diverso. Confrontalo con l'asserzione AreEquivalent descritta di seguito.

AreEquivalent / AreNotEquivalent

Questa asserzione mette a confronto due elenchi e considera gli elenchi di articoli uguali come equivalenti indipendentemente dall'ordine. Sfortunatamente, sembra che ci sia un bug nell'implementazione, in quanto questo test fallisce:

[TestMethod] public void AreEqualTest () List itemList1 = nuova lista() new AnObject () SomeValue = 1, new AnObject () SomeValue = 2; Elenco itemList2 = nuova lista() new AnObject () SomeValue = 2, new AnObject () SomeValue = 1; CollectionAssert.AreEquivalent (itemList1, itemList2);     Bug AreEquivalent di Visual Studio  

con il messaggio di errore:

CollectionAssert.AreEquivalent non riuscito. La raccolta prevista contiene 1 occorrenza (s) di. La raccolta effettiva contiene 0 occorrenze. 

Mentre l'attuazione di questa affermazione da parte di NUnit passa:

I lavori equivalenti di NUnit sono corretti

Contiene / DoesNotContain

Questa affermazione verifica che un oggetto sia contenuto in una raccolta:

[TestMethod] public void ContainsTest () List itemList = nuova lista() new AnObject () SomeValue = 1, new AnObject () SomeValue = 2; CollectionAssert.Contains (itemList, new AnObject () SomeValue = 1);   

usando il metodo Equals (se sovrascritto) per eseguire il test di uguaglianza.

IsSubsetOf / IsNotSubsetOf

Questa affermazione verifica che il primo parametro (il sottoinsieme) sia contenuto nella raccolta del secondo parametro (il superset).

[TestMethod] public void SubsetTest () string subset = "abc"; string superset = "1d2c3b4a"; CollectionAssert.IsSubsetOf (subset.ToCharArray (), superset.ToCharArray ());  

Si noti che il test del sottogruppo non verifica l'ordine o la sequenza, ma semplicemente verifica se gli elementi nell'elenco delle sottogruppi sono contenuti nel superset.

Asserzioni di stringhe

Questi metodi sono implementati nella classe Microsoft.VisualStudio.TestTools.UnitTesting.StringAssert:

  • contiene
  • Partite / DoesNotMatch
  • StartsWith / EndsWith

Questi sono discussi dopo.

contiene

Il metodo Contiene asserisce che il sottoinsieme (si noti che questo è il secondo parametro) è contenuto nella stringa (il primo parametro). Ad esempio, questo test passa:

[TestClass] public class StringTests [TestMethod] public void ContainsTest () string subset = "abc"; string superset = "123abc456"; StringAssert.Contains (superset, sottoinsieme);  

Partite / DoesNotMatch

Questo metodo asserisce che la stringa (il primo parametro) corrisponde al modello regex fornito nel secondo parametro.

StartsWith / EndsWith

Questo metodo asserisce che la stringa (il primo parametro) inizia o termina con un'altra stringa (il secondo parametro).


eccezioni

Le eccezioni possono essere testate senza scrivere blocchi try-catch attorno al metodo di test. Ad esempio, mentre potresti scrivere questo:

[TestMethod] public void CatchingExceptionsTest () try Divide (5, 0);  catch (ArgumentOutOfRangeException) // Accetta silenziosamente l'eccezione come valida.  

È molto più leggibile utilizzare l'attributo ExpectedException nel metodo di test:

[TestMethod] [ExpectedException (typeof (ArgumentOutOfRangeException))] public void BadParameterTest () Divide (5, 0);  

Altri attributi utili

Esistono alcuni attributi aggiuntivi utili per l'esecuzione di una serie di test e di test individuali che migliorano la riusabilità e la leggibilità della base del codice di test dell'unità.

Setup / Teardown

Il motore di test delle unità di Visual Studio fornisce quattro ulteriori attributi del metodo:

  • ClassInitialize
  • ClassCleanup
  • TestInitialize
  • TestCleanup

Questi attributi precedono e seguono l'esecuzione di tutti i test all'interno del dispositivo (classe), nonché prima e dopo ogni test nell'attrezzatura.

Si noti che i metodi decorati con questo attributo devono essere statici.

ClassInitialize

Se un metodo è decorato con questo attributo, il codice nel metodo viene eseguito prima di eseguire tutti i test nella fixture. Si noti che questo metodo richiede un parametro TestContext.

Questo metodo è utile per allocare risorse o classi di istanze che fanno affidamento su tutti i test del dispositivo. Una considerazione importante con le risorse e gli oggetti creati durante l'inizializzazione della fixture è che queste risorse e questi oggetti dovrebbero essere considerati di sola lettura. Non è consigliabile che i test cambino lo stato delle risorse e degli oggetti da cui dipendono altri test. Ciò include connessioni a servizi come database e servizi Web la cui connessione potrebbe essere inserita in uno stato non valido come risultato di un errore in un test, invalidando quindi tutti gli altri test. Inoltre, l'ordine in cui vengono eseguiti i test non è garantito. La modifica dello stato di una risorsa e dell'oggetto creato nell'inizializzazione della fixture può causare effetti collaterali, a seconda dell'ordine in cui vengono eseguiti i test.

ClassCleanup

Un metodo decorato con questo attributo è responsabile della disallocazione delle risorse, delle connessioni di chiusura e così via, che sono state create durante l'inizializzazione della classe. Questo metodo verrà sempre eseguito dopo aver eseguito i test all'interno della fixture, indipendentemente dal successo o dall'errore dei test stessi.

TestInitialize

Simile all'attributo ClassInitialize, verrà eseguito un metodo decorato con questo attributo per ogni test prima di eseguire il test. Uno degli scopi di questo attributo è garantire che le risorse o gli oggetti allocati dal codice ClassInitialize siano inizializzati in uno stato noto prima di eseguire ogni test.

TestCleanup

Completando l'attributo TestInitialize, i metodi decorati con TestCleanup verranno eseguiti al completamento di ogni test.

Setup e flusso di Teardown

Il codice seguente dimostra il flusso di installazione e impostazione e collaudo del test in relazione ai test effettivi:

[TestClass] public class SetupTeardownFlow [ClassInitialize] public static void SetupFixture (contesto TestContext) Debug.WriteLine ("Fixture Setup.");  [ClassCleanup] public static void TeardownFixture () Debug.WriteLine ("Fixture Teardown.");  [TestInitialize] public void SetupTest () Debug.WriteLine ("Test Setup.");  [TestCleanup] public void TeardownTest () Debug.WriteLine ("Test Teardown.");  [TestMethod] public void TestA () Debug.WriteLine ("Test A.");  [TestMethod] public void TestB () Debug.WriteLine ("Test B.");  

L'esecuzione di questa fixture comporta la seguente traccia di output di debug:

Installazione del dispositivo. Configurazione di prova. Test A. Test Teardown. Configurazione di prova. Test B. Test Teardown. Apparecchio Teardown. 

Come illustrato nell'esempio precedente, la fixture viene inizializzata, quindi per ogni test viene eseguito il setup di test e il codice di teardown, seguito dal teardown del fixture alla fine.


Attributi meno frequentemente usati

La seguente sezione descrive gli attributi meno comunemente usati.

AssemblyInitialize / AssemblyCleanup

I metodi decorati con questo attributo devono essere statici e vengono eseguiti quando l'assembly viene caricato. Questo solleva la domanda: cosa succede se l'assemblaggio ha più di un dispositivo di prova?

[TestClass] Fixture1 public class [AssemblyInitialize] public static void AssemblyInit (contesto TestContext) // ... some operation [TestClass] public class Fixture2 [AssemblyInitialize] public static void AssemblyInit (contesto TestContext) // ... qualche operazione  

Se si prova questo, il motore di prova non riesce a eseguire alcun test unitario, riportando:

"UTA013: UnitTestExamplesVS2008.Fixture2: impossibile definire più di un metodo con l'attributo AssemblyInitialize all'interno di un assembly."

Pertanto, può esistere un solo metodo AssemblyInitialize e un metodo AssemblyCleanup per un assieme, indipendentemente dal numero di dispositivi di prova in tale assieme. Si raccomanda quindi di non inserire test effettivi nella classe che definisce questi metodi:

[TestClass] public class AssemblyFixture [AssemblyInitialize] public static void AssemblySetup (contesto TestContext) Debug.WriteLine ("Assembly Initialize.");  [AssemblyCleanup] public static void AssemblyTeardown () Debug.WriteLine ("Assembly Cleanup.");  

risultando nella seguente sequenza di esecuzione:

Assembly Initialize. Installazione del dispositivo. Configurazione di prova. Test A. Test Teardown. Configurazione di prova. Test B. Test Teardown. Apparecchio Teardown. Pulizia del gruppo. 

Notare che l'assembly aggiuntivo inizializza e pulisce le chiamate.

Ignorare

Questo metodo può decorare metodi specifici o interi dispositivi.

Ignora un metodo di prova

Se questo attributo decora un metodo di prova:

[TestMethod, Ignore] public void TestA () Debug.WriteLine ("Test A.");  

il test non verrà eseguito. Sfortunatamente, il riquadro Risultati test di Visual Studio non indica che esistono test attualmente ignorati:

I test ignorati non vengono mostrati

Confrontalo con NUnit, che mostra chiaramente i test ignorati:

NUnit mostra i test ignorati

Il display NUnit contrassegna l'intero albero di test come "sconosciuto" quando uno o più metodi di test sono contrassegnati come "Ignora".

Ignora un dispositivo di prova

I metodi di un'intera fixture possono essere ignorati usando l'attributo Ignore a livello di classe:

[TestClass, Ignora] public class SetupTeardownFlow ... ecc ... 

Cancellare la cache di test

Se si aggiunge l'attributo Ignora a un metodo, è possibile notare che Visual Studio esegue ancora il test. È necessario cancellare la cache di test per Visual Studio per raccogliere la modifica. Un modo per farlo è pulire la soluzione e ricostruirla.

Proprietario

Utilizzato per scopi di reporting, questo attributo descrive la persona responsabile del metodo di test unitario.

DeploymentItem

Se i test delle unità vengono eseguiti in una cartella di distribuzione separata, questo attributo può essere utilizzato per specificare i file necessari per eseguire una classe di test o un metodo di prova. È possibile specificare file o cartelle da copiare nella cartella di distribuzione e, facoltativamente, specificare il percorso di destinazione relativo alla cartella di distribuzione.

Descrizione

Utilizzato per la segnalazione, questo attributo fornisce una descrizione del metodo di prova. Stranamente, questo attributo è disponibile solo sui metodi di prova e non è disponibile nelle classi di test.

HOSTTYPE

Per i metodi di test, questo attributo viene utilizzato per specificare l'host in cui verrà eseguito il test dell'unità.

Priorità

Questo attributo non è utilizzato dal motore di test, ma potrebbe essere utilizzato, tramite riflessione, dal proprio codice di test. L'utilità di questo attributo è discutibile.

Oggetto da lavoro

Se si utilizza Team Foundation Server (TFS), è possibile utilizzare questo attributo su un metodo di prova per specificare l'ID dell'oggetto di lavoro assegnato da TFS al test unità specifico.

CssIteration / CssProjectStructure

Questi due attributi vengono utilizzati in relazione a TeamBuild e TestManagementService e consentono di specificare un'iterazione del progetto a cui corrisponde il metodo di prova.


Test parametrizzati con l'attributo DataSource

Il motore di test unitario di Microsoft supporta CSV, XML o origini dati del database per test parametrizzati. Questo non è esattamente un test parametrico vero (vedi come NUnit implementa il test parametrizzato) perché i parametri non sono passati al metodo di test unitario ma devono essere estratti dall'origine dati e passati al metodo sotto test. Tuttavia, la capacità di caricare i dati di test in un DataTable da una varietà di fonti è utile per guidare l'automazione del test.

Fonte dei dati CSV

Un file di testo con valori separati da virgola può essere utilizzato per un'origine dati:

Numeratore, Denominatore, ExpectedResult 10, 5, 2 20,5, 4 33, 3, 11 

e utilizzato in un metodo di prova:

[TestClass] public class DataSourceExamples public TestContext TestContext get; impostato;  [TestMethod] [DataSource ("Microsoft.VisualStudio.TestTools.DataSource.CSV", "C: \\ temp \\ csvData.txt", "csvData # txt", DataAccessMethod.Sequential)] public void CsvDataSourceTest () int n = Convert.ToInt32 (TestContext.DataRow ["Numerator"]); int d = Convert.ToInt32 (TestContext.DataRow ["Denominator"]); int r = Convert.ToInt32 (TestContext.DataRow ["ExpectedResult"]); Debug.WriteLine ("n =" + n + ", d =" + d + ", r =" + r);  

Risulta il seguente risultato:

n = 10, d = 5, r = 2 n = 20, d = 5, r = 4 n = 33, d = 3, r = 11 

Si noti che la finestra dei risultati del test non mostra le esecuzioni dei parametri (contrasto a NUnit):

Risultati dei test parametrizzati

Tuttavia, ci sono evidenti vantaggi nel non visualizzare ciascuna combinazione di test, specialmente per dataset di grandi dimensioni.

Origine dati XML

Dato un file XML come:

    

un esempio di utilizzo di un'origine dati XML per un test di unità è:

[TestMethod] [DataSource ("Microsoft.VisualStudio.TestTools.DataSource.XML", "C: \\ temp \\ xmlData.xml", "Row", DataAccessMethod.Sequential)] public void XmlDataSourceTest () int n = Convert .ToInt32 (TestContext.DataRow [ "numeratore"]); int d = Convert.ToInt32 (TestContext.DataRow ["Denominator"]); int r = Convert.ToInt32 (TestContext.DataRow ["ExpectedResult"]); Debug.WriteLine ("n =" + n + ", d =" + d + ", r =" + r);  

Si noti che oltre ai parametri degli attributi dell'origine dati, il codice di test è lo stesso.

Origine dati del database

Una tabella di database può anche essere utilizzata come origine dati. Dato un tavolo come:

Tabella del database come origine dati

e dati:

Dati di test del database

Un metodo di prova di esempio che utilizza questi dati è simile a:

[TestMethod] [DataSource ("System.Data.SqlClient", "Origine dati = INTERACX-HP; Initial Catalog = UnitTesting; Integrated Security = True", "DivideTestData", DataAccessMethod.Sequential)] public void XmlDataSourceTest () int n = Convert.ToInt32 (TestContext.DataRow ["Numerator"]); int d = Convert.ToInt32 (TestContext.DataRow ["Denominator"]); int r = Convert.ToInt32 (TestContext.DataRow ["ExpectedResult"]); Debug.WriteLine ("n =" + n + ", d =" + d + ", r =" + r);  

Di nuovo, osservate che il codice del test stesso è lo stesso, l'unica cosa che abbiamo fatto qui è cambiare la definizione DataSource.

Attributo TestProperty

La documentazione MSDN per questo attributo illustra la dichiarazione di una coppia nome-valore TestProperty e quindi, utilizzando la reflection, l'acquisizione del nome e del valore. Questo sembra essere un modo ottuso di creare test parametrizzati. 

Inoltre, il codice, descritto sul blog di Craig Andera, per utilizzare l'attributo TestProperty per parametrizzare il processo di inizializzazione del test non influisce sulla raccolta TestContext.Properties su Visual Studio 2008 o Visual Studio 2012.