Questo è un estratto dall'eBook Testing Unit Affinity, di Marc Clifton, gentilmente fornito da Syncfusion.
In questo articolo, inizieremo a parlare di alcuni degli argomenti avanzati relativi ai test unitari. Ciò include cose come complessità ciclometrica, metodi, campi e riflessioni.
La complessità ciclometrica è una misura del numero di percorsi indipendenti attraverso il codice. Il test di copertura del codice ha lo scopo di garantire che i test eseguano tutti i possibili percorsi di codice. Il test di copertura del codice è disponibile nelle versioni Test, Premium e Ultimate di Visual Studio. La copertura del codice non fa parte di NUnit. È disponibile anche una soluzione di terze parti, NCover.
Per illustrare questo problema, considera questo codice, che analizza i parametri della riga di comando:
public static class CommandLineParser /// /// Restituisce un elenco di opzioni basate sull'analisi della forza bruta /// delle opzioni della riga di comando. La funzione restituisce le opzioni /// specificate e il parametro option se necessario. /// Dizionario statico pubblicoParse (string cmdLine) Dictionary opzioni = nuovo dizionario (); string [] items = cmdLine.Split ("); int n = 0; while (n < items.Length) string option = items[n]; string param = String.Empty; if (option[0] != '-') throw new ApplicationException("Expected an option."); if (option == "-f") // Has the parameter been supplied? if (items.Length <= n + 1) throw new ApplicationException("Filename not specified."); param = items[n + 1]; // Is it a parameter or another option? if (param[0] == '-') throw new ApplicationException("Filename not specified."); ++n; // Skip the filename option parameter. options[option] = param; ++n; return options;
e un paio di semplici test (si noti che questi test omettono i percorsi del codice che comportano l'esclusione di eccezioni):
[TestFixture] public class CodeCoverageTests [Test] public void CommandParserTest () Dictionaryoptions = CommandLineParser.Parse ("- a -b"); Assert.That (options.Count == 2, "Count expected to be 2"); Assert.That (options.ContainsKey ("- a"), "Expected option '-a'"); Assert.That (options.ContainsKey ("- b"), "Expected option '-b'"); [Test] public void FilenameParsingTest () Dictionary options = CommandLineParser.Parse ("- f foobar"); Assert.That (options.Count == 1, "Count expected to be 1"); Assert.That (options.ContainsKey ("- f"), "Expected option '-f'"); Assert.That (opzioni ["- f"] == "foobar");
Ora diamo un'occhiata a cosa potrebbe sembrare un test di copertura del codice, prima scrivendo un aiutante per la copertura del codice di un povero uomo:
Copertura statica pubblica Public static List CoveragePoints get; set; public static void Reset () CoveragePoints = new List (); [Condizionale ("DEBUG")] pubblico statico vuoto Set (int coveragePoint) CoveragePoints.Add (coveragePoint);
Avremo anche bisogno di un semplice metodo di estensione; vedrai perché in un minuto:
classe statica pubblica ListExtensions public static bool HasOrderedItems (this List itemList, int [] items) int n = 0; foreach (int i in itemList) if (i! = items [n]) return false; ++ n; return true;
Ora possiamo aggiungere setpoint di copertura nel nostro codice, che verrà compilato nell'applicazione quando è compilato in modalità DEBUG (le linee rosse in grassetto dove aggiunto):
public static class CommandLineParser /// /// Restituisce un elenco di opzioni basate sull'analisi della forza bruta /// delle opzioni della riga di comando. La funzione restituisce le opzioni /// specificate e il parametro option se necessario. /// Dizionario statico pubblicoParse (string cmdLine) Dictionary opzioni = nuovo dizionario (); string [] items = cmdLine.Split ("); int n = 0; while (n < items.Length) Coverage.Set(1); // WE ADD THIS COVERAGE SET-POINT string option = items[n]; string param = String.Empty; if (option[0] != '-') throw new ApplicationException("Expected an option."); if (option == "-f") Coverage.Set(2); // WE ADD THIS COVERAGE SET-POINT // Has the parameter been supplied? if (items.Length <= n + 1) throw new ApplicationException("Filename not specified."); param = items[n + 1]; // Is it a parameter or another option? if (param[0] == '-') throw new ApplicationException("Filename not specified."); ++n; // Skip the filename option parameter. options[option] = param; ++n; return options;
E ora possiamo scrivere il seguente dispositivo di prova:
[TestFixture] public class CommandParserCoverageTests [SetUp] public void CoverageSetup () Coverage.Reset (); [Test] public void CommandParserTest () Dictionaryoptions = CommandLineParser.Parse ("- a -b"); Assert.That (Coverage.CoveragePoints.HasOrderedItems (new [] 1, 1)); [Test] public void FilenameParsingTest () Dictionary options = CommandLineParser.Parse ("- f foobar"); Assert.That (Coverage.CoveragePoints.HasOrderedItems (new [] 1, 2));
Si noti come ora stiamo ignorando i risultati effettivi, ma stiamo garantendo che i blocchi di codice desiderati vengano eseguiti.
Probabilmente, un test unitario dovrebbe riguardare solo se stesso con campi e metodi pubblici. Il controargomento per questo è che al fine di testare l'intera implementazione, l'accesso ai campi protetti o privati, per affermare il loro stato, e la capacità di unità test metodi protetti o privati è richiesto. Considerando che probabilmente non è desiderabile esporre la maggior parte dei calcoli di basso livello, e quelli sono esattamente i metodi che si vogliono testare, è molto probabile che sia necessario testare almeno metodi protetti o privati. Ci sono diverse opzioni disponibili.
Questo esempio illustra il concetto:
public class DoesSomething #if TEST public #else private #endif void SomeComputation ()
Sebbene ciò sia fattibile, produce un brutto codice sorgente e corre il serio rischio che qualcuno possa effettivamente chiamare il metodo con il simbolo TEST definito, solo per scoprire che il suo codice si interrompe in una build di produzione in cui il simbolo TEST non è definito.
In alternativa, se i metodi sono protetti, prendere in considerazione la creazione di una classe di test:
public class DoesSomethingElse protected void SomeComputation () public class DoesSomethingElseTesting: DoesSomethingElse public void TestSomeComputation () base.SomeComputation ();
Ciò consente di creare un'istanza della classe di test derivata e accedere a un metodo protetto tramite un metodo esposto pubblicamente nella sottoclasse.
Infine, si può usare la riflessione per metodi privati o classi sigillate. Quanto segue illustra un metodo privato ed esegue tale metodo tramite riflessione in un test:
public class DoesSomething private void SomeComputation () [TestClass] public class DoesSomethingTest [TestMethod] public void SomeComputationTest () DoesSomething ds = new DoesSomething (); Digitare t = ds.GetType (); MethodInfo mi = t.GetMethod ("SomeComputation", BindingFlags.Instance | BindingFlags.NonPublic); mi.Invoke (ds, null);