Detta är ett utdrag från Unit Testing Succinctly eBook, av Marc Clifton, vänligt tillhandahållen av Syncfusion.
I den här artikeln ska vi börja prata om några av de avancerade ämnena som följer med enhetstestning. Detta inkluderar saker som cyklometrisk komplexitet, metoder, fält och reflektion.
Cyklometrisk komplexitet är ett mått på antalet oberoende banor genom din kod. Kodtäcktestning är avsett att se till att dina tester utför alla möjliga kodvägar. Kodtäckningstestning finns i Visual Studio Test, Premium och Ultimate versioner. Kod täckning ingår inte i NUnit. En tredjepartslösning, NCover, finns också tillgänglig.
För att illustrera denna fråga, överväga den här koden, vilken analyserar kommandoradsparametrar:
offentlig statisk klass CommandLineParser /// /// Returnerar en lista med alternativ baserade på brute force parsing /// av kommandoradsalternativ. Funktionen returnerar de /// angivna alternativen och alternativparametern om det behövs. /// offentliga statiska ordbokenParse (sträng cmdLine) Dictionary alternativ = ny ordbok (); sträng [] items = cmdLine.Split ("); int n = 0; medan (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;
och ett par enkla test (observera att dessa tester förlorar kodvägarna som leder till att undantag kastas):
[TestFixture] offentlig klass CodeCoverageTests [Test] public void CommandParserTest () Ordbokoptions = CommandLineParser.Parse ("- a -b"); Assert.That (options.Count == 2, "Count förväntas vara 2"); Assert.That (options.ContainsKey ("- a"), "Expected option '-a'"); Assert.That (options.ContainsKey ("- b"), "Förväntat alternativ" -b ""); [Test] public void FilnamnParsingTest () Dictionary alternativ = CommandLineParser.Parse ("- f foobar"); Assert.That (options.Count == 1, "Count förväntas vara 1"); Assert.That (options.ContainsKey ("- f"), "Förväntat alternativ" -f ""); Assert.That (alternativ ["- f"] == "foobar");
Låt oss nu titta på vad ett kodtäckningstest kan se ut, först genom att skriva en fattig mans kodningshjälpare:
offentlig statisk klass täckning offentliga statiska listan täckningspoäng get; set; statisk statisk tomgång Återställ () CoveragePoints = ny lista (); [Villkorlig ("DEBUG")] statisk statisk tomgång (int coveragePoint) CoveragePoints.Add (coveragePoint);
Vi behöver också en enkel förlängningsmetod. Du kommer att se varför i en minut:
offentlig statisk klass ListExtensions public static bool HasOrderedItems (denna List itemList, int [] items) int n = 0; föreach (int jag i objektlista) om (i! = objekt [n]) return false; ++ n; returnera sant;
Nu kan vi lägga till täckningsinställningar i vår kod, som kommer att sammanställas i applikationen när den är sammanställd i DEBUG-läget (de djärva röda linjerna där tillagda):
offentlig statisk klass CommandLineParser /// /// Returnerar en lista med alternativ baserade på brute force parsing /// av kommandoradsalternativ. Funktionen returnerar de /// angivna alternativen och alternativparametern om det behövs. /// offentliga statiska ordbokenParse (sträng cmdLine) Dictionary alternativ = ny ordbok (); sträng [] items = cmdLine.Split ("); int n = 0; medan (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;
Och nu kan vi skriva följande testfixtur:
[TestFixture] public class CommandParserCoverageTests [SetUp] public void CoverageSetup () Däck.Reset (); [Test] public void CommandParserTest () Dictionaryoptions = CommandLineParser.Parse ("- a -b"); Assert.That (Coverage.CoveragePoints.HasOrderedItems (nytt [] 1, 1)); [Test] public void FilnamnParsingTest () Dictionary alternativ = CommandLineParser.Parse ("- f foobar"); Assert.That (Coverage.CoveragePoints.HasOrderedItems (new [] 1, 2));
Observera hur vi nu ignorerar de faktiska resultaten men ser till att de önskade kodblocken utförs.
Förmodligen bör ett test endast omfatta offentliga fält och metoder. Motargumentet för detta är att för att kunna testa hela implementeringen, åtkomst till skyddade eller privata fält, för att hävda sitt tillstånd, och förmågan att enhetstestskyddade eller privata metoder krävs. Med tanke på att det förmodligen inte är önskvärt att avslöja de flesta lågnivåberäkningar, och det är exakt de metoder man vill testa, är det troligt att det är nödvändigt att testa minst skyddade eller privata metoder. Det finns flera alternativ tillgängliga.
Detta exempel illustrerar konceptet:
offentlig klass DoesSomething #if TEST public #else privat #endif void SomeComputation ()
Även om detta är genomförbart, producerar den ful källkod och kör den allvarliga risken för att någon faktiskt kan ringa metoden med symbolen TEST definierad, bara för att upptäcka att hans eller hennes kod bryts i en produktionsbyggnad där TEST-symbolen är odefinierad.
Som ett alternativ, om metoderna är skyddade, överväg att avleda en testklass:
public class DoesSomethingElse protected void SomeComputation () offentlig klass DoesSomethingElseTesting: DoesSomethingElse public void TestSomeComputation () base.SomeComputation ();
Detta gör att du kan instansera den härledda testklassen och få tillgång till en skyddad metod via en offentligt exponerad metod i underklassen.
Slutligen kan man använda reflektion för privata metoder eller förseglade klasser. Följande illustrerar en privat metod och utför den metoden genom reflektion i ett test:
offentlig klass DoesSomething privat void SomeComputation () [TestClass] offentlig klass DoesSomethingTest [TestMethod] public void SomeComputationTest () DoesSomething ds = new DoesSomething (); Skriv t = ds.GetType (); MethodInfo mi = t.GetMethod ("SomeComputation", BindingFlags.Instance | BindingFlags.NonPublic); mi.Invoke (ds, null);