Unitatea de testare succintă Testarea avansată a unității

Acesta este un extras din Cartea electronică de testare a unității, de Marc Clifton, oferită cu amabilitate de Syncfusion.

În acest articol, vom începe să vorbim despre câteva dintre subiectele avansate care vin cu testarea unităților. Aceasta include lucruri cum ar fi complexitatea ciclometrică, metode, câmpuri și reflecție.

Complexitatea ciclometrică

Complexitatea ciclometrică este o măsură a numărului de căi independente prin codul dvs. Testarea codului de acoperire are scopul de a vă asigura că testele dvs. execută toate căile de cod posibile. Testarea codului de acoperire este disponibilă în versiunile de testare, Premium și Ultimate ale Visual Studio. Acoperirea codului nu face parte din NUnit. O soluție terță parte, NCover, este, de asemenea, disponibilă.

Pentru a ilustra această problemă, ia în considerare acest cod, care analizează parametrii liniei de comandă:

clasa publică statică CommandLineParser /// /// Returnează o listă de opțiuni pe baza analizei forței brute /// a opțiunilor din linia de comandă. Funcția returnează /// opțiunile specificate și parametrul opțiune, dacă este necesar. /// public static Dicționar Parse (șirul cmdLine) dicționar opțiuni = dicționar nou(); șir [] elemente = cmdLine.Split ("); int n = 0; în timp ce (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;   

și câteva teste simple (rețineți că aceste teste omit căile de cod care duc la extragerea unor excepții):

[TestFixture] clasă publică CodeCoverageTests [Test] public void CommandParserTest () Dicționar opțiuni = CommandLineParser.Parse ("- a -b"); Assert.That (opțiuni.Count == 2, "Count așteptat să fie 2"); Assert.That (opțiuni.ContainsKey ("- a"), "Opțiune așteptată" -a ""); Assert.That (options.ContainsKey ("- b"), "Opțiunea așteptată" -b "");  [Test] public void FilenameParsingTest () Dicționar opțiuni = CommandLineParser.Parse ("- f foobar"); Assert.That (opțiuni.Count == 1, "Count așteptat să fie 1"); Assert.That (opțiunile.ContainsKey ("- f"), "Opțiunea așteptată" -f ""); Assert.That (opțiuni ["- f"] == "foobar");  

Acum, să aruncăm o privire la ceea ce ar putea arăta un test de acoperire a codului, mai întâi scriind un ajutor pentru acoperirea codului unui om sărac:

clasa publică statică Acoperire public static List CoveragePoints get; set; void static public Resetare () CoveragePoints = new List ();  [Condiționat ("DEBUG")] set public void static (int coveragePoint) CoveragePoints.Add (coveragePoint);  

Avem de asemenea nevoie de o metodă de extensie simplă; veți vedea de ce într-un minut:

public statică statică ListExtensions boot static public HasOrderedItems (acest ListList, int [] items) int n = 0; foreach (int i în itemList) if (i! = elemente [n]) return false;  ++ n;  return true;  

Acum putem adăuga puncte de acoperire în codul nostru, care vor fi compilate în aplicație atunci când sunt compilate în modul DEBUG (liniile roșii aldine în care sunt adăugate):

clasa publică statică CommandLineParser /// /// Returnează o listă de opțiuni pe baza analizei forței brute /// a opțiunilor din linia de comandă. Funcția returnează /// opțiunile specificate și parametrul opțiune, dacă este necesar. /// public static Dicționar Parse (șirul cmdLine) dicționar opțiuni = dicționar nou(); șir [] elemente = cmdLine.Split ("); int n = 0; în timp ce (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;   

Și acum putem scrie următorul test:

[TestFixture] clasa publică CommandParserCoverageTests [SetUp] public void CoverageSetup () Coverage.Reset ();  [Test] public void CommandParserTest () Dicționar opțiuni = CommandLineParser.Parse ("- a -b"); Assert.That (Acoperire.CoveragePoints.HasOrderedItems (nou [] 1, 1));  [Test] public void FilenameParsingTest () Dicționar opțiuni = CommandLineParser.Parse ("- f foobar"); Assert.That (Acoperire.CoveragePoints.HasOrderedItems (nou [] 1, 2));  

Rețineți modul în care ignorăm acum rezultatele efective, dar asigurați-vă că sunt executate blocurile de cod dorite.

Testarea cutie albă: Inspectarea câmpurilor și a metodelor protejate și private

În mod cert, un test unitar ar trebui să se refere numai la câmpurile și metodele publice. Contraargumentul pentru aceasta este că, pentru a testa întreaga implementare, accesul la câmpurile protejate sau private, pentru a-și afirma starea și capacitatea de a efectua unități de testare protejate sau private este necesară. Având în vedere că probabil nu este de dorit să se expună cele mai multe calcule la nivel scăzut, și acestea sunt exact metodele pe care le dorește să le testeze, este foarte probabil ca testarea metodelor cel puțin protejate sau private să fie necesară. Există mai multe opțiuni disponibile.

Expunerea metodelor și câmpurilor în modul de testare

Acest exemplu ilustrează conceptul:

clasa publica Are ceva Ceva #de TEST public #else privat #endif void SomeComputation ()  

În timp ce acest lucru este posibil, produce un cod sursă urât și are un risc serios ca cineva să numească metoda cu simbolul TEST definit, doar pentru a descoperi că codul său se rupe într-o construcție de producție în care simbolul TEST este nedefinit.

Derivarea unei clase de test

Ca alternativă, dacă metodele sunt protejate, luați în considerare o clasă de testare:

clasa publica DoesSomethingElse void protejat SomeComputation ()  clasa publica DoesSomethingElseTesting: DoesSomethingElse public void TestSomeComputation () base.SomeComputation ();  

Aceasta vă permite să instanțiați clasa de testare derivată și să accesați o metodă protejată printr-o metodă expusă public în subclasă.

Reflecţie

În cele din urmă, se poate folosi reflecția pentru metode private sau clase sigilate. Următoarele ilustrează o metodă privată și execută această metodă prin reflecție într-un test:

clasa publica DoesSomething private void SomeComputation ()  [TestClass] clasa publica DoesSomethingTest [TestMethod] public void SomeComputationTest () DoesSomething ds = new DoesSomething (); Tipul t = ds.GetType (); MetodăInfo mi = t.GetMethod ("SomeComputation", BindingFlags.Instance | BindingFlags.NonPublic); mi.Invoke (ds, null);  
Cod