Questo è un estratto dall'eBook Testing Unit Affinity, di Marc Clifton, gentilmente fornito da Syncfusion.
Un motore di test unitario in un linguaggio riflessivo (come qualsiasi linguaggio .NET) ha tre parti:
Questo articolo fornisce esempi di codice di come funziona, mettendo insieme un semplice motore di test unitario. Se non sei interessato all'indagine sotto il cofano dei motori di test unitari, sentiti libero di saltare questo articolo.
Il codice qui presuppone che stiamo scrivendo un motore di prova rispetto agli attributi di test dell'unità Visual Studio definiti nell'assembly Microsoft.VisualStudio.QualityTools.UnitTestFramework. Altri motori di prova di unità possono utilizzare altri attributi per gli stessi scopi.
Architettonicamente, i test di unità dovrebbero risiedere in un assembly separato dal codice in fase di test o, almeno, devono essere inclusi nell'assembly solo se è stato compilato in modalità "Debug". Il vantaggio di mettere i test unitari in un assembly separato è che si può anche testare l'unità del debug, versione di produzione ottimizzata del codice.
Detto questo, il primo passo è caricare il gruppo:
bool statico LoadAssembly (string assemblyFilename, out Assembly assembly, out string issue) bool ok = true; issue = String.Empty; assy = null; try assy = Assembly.LoadFile (assemblyFilename); catch (Exception ex) issue = "Errore nel caricamento dell'assembly:" + ex.Message; ok = falso; return ok;
Si noti che i motori di prova dell'unità professionale caricano i gruppi in un dominio applicazione separato in modo che l'assemblaggio possa essere scaricato o ricaricato senza riavviare il motore di test dell'unità. Ciò consente anche la ricompilazione del gruppo di test dell'unità e dei gruppi dipendenti senza prima arrestare il motore di test dell'unità.
Il passaggio successivo consiste nel riflettere sull'assieme per identificare le classi che sono designate come "test fixture" e all'interno di tali classi per identificare i metodi di test. Un set di base di quattro metodi supporta i requisiti minimi del motore di test unitario, la scoperta di dispositivi di prova, metodi di prova e attributi di gestione delle eccezioni:
////// Restituisce un elenco di classi nell'assembly fornito che hanno un attributo "TestClass". /// IEnumerable staticoGetTestFixtures (Assembly assy) return assy.GetTypes (). Where (t => t.GetCustomAttributes (typeof (TestClassAttribute), false) .Length == 1); /// /// Restituisce un elenco di metodi nel dispositivo di prova che sono decorati con l'attributo "TestMethod". /// IEnumerable staticoGetTestMethods (Type testFixture) return testFixture.GetMethods (). Where (m => m.GetCustomAttributes (typeof (TestMethodAttribute), false) .Length == 1); /// /// Restituisce un elenco di attributi specifici che possono essere decorati con il metodo. /// IEnumerable staticoGetMethodAttributes (Metodo MethodInfo) return method.GetCustomAttributes (typeof (AttrType), false) .Cast (); /// /// Restituisce true se il metodo è decorato con un attributo "ExpectedException" mentre il tipo di eccezione è l'eccezione prevista. /// bool statico IsExpectedException (metodo MethodInfo, Exception expectedException) Type expectedExceptionType = expectedException.GetType (); restituire GetMethodAttributes(metodo). Dove (attr => attr.ExceptionType == expectedExceptionType) .Count ()! = 0;
Una volta che queste informazioni sono state compilate, il motore richiama i metodi di test all'interno di un blocco try-catch (non vogliamo che il motore di test dell'unità si blocchi in modo anomalo):
runTests statici void (Type testFixture, Actionrisultato) IEnumerable testMethods = GetTestMethods (testFixture); if (testMethods.Count () == 0) // Non fare nulla se non ci sono metodi di test. ritorno; object inst = Activator.CreateInstance (testFixture); foreach (MethodInfo mi in testMethods) bool pass = false; prova // I metodi di test non hanno parametri. mi.Invoke (inst, null); pass = true; catch (Exception ex) pass = IsExpectedException (mi, ex.InnerException); finally result (testFixture.Name + "." + mi.Nome + ":" + (pass? "Pass": "Fail"));
Infine, possiamo mettere insieme questo codice in una semplice applicazione di console che prende l'assembly di test unitario come risultato di parametri in un motore utilizzabile, ma semplice:
usando il sistema; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; utilizzando Microsoft.VisualStudio.TestTools.UnitTesting; namespace SimpleUnitTestEngine class Program static void Main (string [] args) string issue; if (! VerifyArgs (args, out issue)) Console.WriteLine (issue); ritorno; Assy di assemblaggio; if (! LoadAssembly (args [0], out assy, out issue)) Console.WriteLine (issue); ritorno; IEnumerabletestFixtures = GetTestFixtures (assy); foreach (Type testFixture in testFixtures) RunTests (testFixture, t => Console.WriteLine (t)); static bool VerifyArgs (string [] args, out string issue) bool ok = true; issue = String.Empty; if (args.Length! = 1) issue = "Utilizzo: SimpleUnitTestEngine "; ok = false; else string assemblyFilename = args [0]; if (! File.Exists (assemblyFilename)) issue =" Il nome file '"+ args [0] +"' non esiste. "; ok = false; restituisce ok; ... il resto del codice ...
Il risultato dell'esecuzione di questo semplice motore di test viene visualizzato in una finestra della console, ad esempio:
I nostri risultati della console Simple Test Engine