.
Anmeldung | Registrieren | Hilfe | Posteingang
Suchen
Home Foren News Member Offers Termine Developer Blogs Knowledge Base

Navigation

Navigationslinks überspringen.
Knowledge Base reduzierenKnowledge Base
Tutorials reduzierenTutorials
Webentwicklung
Cliententwicklung
Datenbankentwicklung
IT Professional
Sharepoint
Sprachspezifisch reduzierenSprachspezifisch
C#
Visual Basic
C++
XAML
SQL
JavaScript
Erfahrungsberichte reduzierenErfahrungsberichte
Entwicklersoftware
Bücher
FAQ Grundlagen

Verknüpfungen

  • Knowledge Base durchsuchen
  • Hilfe zur Knowledge Base
  • RSS Feed
  • Twitter

Im Test: NCover

Unbenannte Seite

Zusammenfassung

Der Artikel behandelt das Programm NCover. Unter Verwendung der Programmiersprache C# und des Unit Test Frameworks NUnit wird gezeigt, wie sich mithilfe von NCover die Testabdeckung eines Programmcodes durch Unit Tests ermitteln lässt. Des Weiteren wird im Artikel kurz auf das Thema Unit Testing eingegangen.

NCover! N was?

clip_image002Die N-Serie sollte unter .Net Entwicklern bekannt sein. Damit sind alle Tools gemeint, die mit einem großen N beginnen, z. B. NUnit, NArrange, NMock usw. Seinen Ursprung hat das Ganze in der Java-Szene, in der bestimmten Java-Hilfsprogrammen bzw. Java-Bibliotheken einfach ein großes J vorangestellt wurde, wie z. B. JUnit, um auszudrücken, dass es sich hierbei um ein Werkzeug bzw. um eine Bibliothek für Java handelt. Das große N bei der N-Serie steht natürlich für .Net. Seit ca. vier Jahren tummelt sich unter der N-Serie auch das Werkzeug NCover. Dies ist ursprünglich als Open Source Projekt entstanden und wurde jetzt kommerzialisiert.

NCover bezeichnet sich selbst als Code Coverage Tool bzw. als Code Coverage Analyzer. Darunter versteht man ein Programm, das einen Code zur Laufzeit überwacht und Aufzeichnungen darüber macht, welche Codezeilen ausgeführt werden. NCover analysiert den Code auf Basis des .Net Runtime Layer, d. h. NCover funktioniert prinzipiell mit jeder .Net Sprache, also jeder Sprache, die in Managed Code übersetzt wird. Explizit wurde NCover mit C#, VB.NET, Managed C++, J#, MSIL und Boo getestet, jedoch sollte das Programm auch mit jeder anderen .Net Sprache problemlos zusammenarbeiten.

Dabei ist ein Code Covarge Analyzer nicht mit einen Profiler zu verwechseln. Ein Profiler hat die Aufgabe herauszufinden, welche Programmteile welche Ausführungszeit benötigen, damit zeitkritische Programmteile gezielt identifiziert und optimiert werden können. Bei NCover geht es nicht darum, Ausführungszeiten zu ermitteln, sondern einzig und allein darum herauszufinden, welche Teile des Programmes tatsächlich durchlaufen werden.

Bevor ich tiefer in das Thema Code Coverage einsteige, möchte ich einen kurzen Exkurs in das Thema Unit Testing geben, damit auch von Lesern, die mit Unit Testing noch keine bzw. erst sehr wenige Erfahrungen gesammelt haben, der volle Nutzen von Code Covarage Tools verstanden werden kann. Wer mit diesen Themen bereits bestens vertraut ist, kann die entsprechenden Abschnitte einfach überfliegen.

Unit Testing: ein gepimptes Assert

Im Folgenden möchte ich kurz beschreiben, was sich hinter dem Begriff Unit Testing verbirgt. Unit Testing bedeutet automatisiertes Testen von Programmcode. In einigen Prozessmodellen, wie z. B. Test Driven Development, überlegt man sich schon vor dem Programmieren automatisierte Tests.

Im Wesentlichen ist ein Unit Tests nichts anderes als ein „Assert“. Eine Assertion, zu Deutsch Zusicherung, ist eine Aussage, bei der man davon ausgeht, dass sie immer „wahr“ ist. Sollte dies nicht der Fall sein, so erhält man als Ergebnis eine „fehlgeschlagene Assertion“.

Um zu verdeutlichen, wie ein Assert unter .Net mit C# aussieht und wie sich eine „fehlgeschlagene Assertion“ bemerkbar macht, hier ein kurzes Beispiel:

1: class Program
2: {
3:     static void Main(string[] args)
4:     {
5:         // Diese Assertion wird nur im Debug Modus geprüft
6:         System.Diagnostics.Debug.Assert(true);
7:         
8:         // Diese Assertion wird auch im Release Modus geprüft
9:         System.Diagnostics.Trace.Assert(true);
10:  
11:         // Diese Exception schlägt im Debug Modus fehl
12:         System.Diagnostics.Debug.Assert(false);
13:     }
14: }

Führt man dieses Programm im Debug Modus aus, so erhält man eine Fehlermeldung:

clip_image002[5]

Die Fehlermeldung zeigt an, in welcher Datei und in welcher Zeile die Assertion fehlgeschlagen ist.

Um Programmcode automatisiert zu testen, könnte man jetzt diverse Testmethoden anlegen, die mithilfe von Assertions sicherstellen, dass z. B. bestimmte Methoden korrekte Ergebnisse berechnen.

1: // Soll Anwendung im UNIT_TEST Modus oder im Normal Modus ausgeführt werden? 
2: #define UNIT_TEST   
3:  
4: class Program
5: {
6:     static int Add(int a, int b)
7:     {
8:         return 4;
9:     }
10:  
11:     static void AddTest()
12:     {
13:         System.Diagnostics.Debug.Assert(Add(2, 2) == 4);
14:         System.Diagnostics.Debug.Assert(Add(3, 2) == 5);
15:     }
16:  
17:     static void Main(string[] args)
18:     {
19:         #if UNIT_TEST  
20:         AddTest();
21:         #else
22:         System.Console.WriteLine("Normaler Programmablauf");
23:         System.Console.Read();
24:         #endif
25:     }
26: }

Kommentiert man die Zeile #define UNIT_TEST aus, dann wird das Programm ganz normal durchlaufen, ansonsten werden die Tests zur Methode Add durchlaufen. Alternativ kann man das Definieren der Präprozessorkonstante (#define UNIT_TEST) auch von Visual Studio mithilfe einer speziellen Konfiguration erledigen lassen. Dazu legt man im Konfigurations-Manager von Visual Studio neben den Konfigurationen für Debug und Release Modus noch eine spezielle Unit Test Konfiguration an, die an sich nichts anderes macht, als die Präprozessorkonstante UNIT_TEST zu definieren. Das heißt nach dem Anlegen dieser Konfiguration kann die Anwendung in den Varianten Debug, Release und Unit Test laufen. Debug und Release verhalten sich wie bekannt, Unit Test durchläuft die Tests.

clip_image002[1]

Doch Präprozessorkonstanten waren gestern. Heute löst man so ein Problem eleganter mithilfe von Reflection und Methodenattributen. Die Idee dahinter ist ganz einfach: Man kennzeichnet jede Testmethode mit einem speziellen Attribut, z. B. „MyUnitTest“. Danach erstellt man ein Programm, das mithilfe von Reflection alle so gekennzeichneten Methoden einfach der Reihe nach aufruft und bei fehlgeschlagenen Assertionen eine Fehlermeldung ausgibt.

1: using System;
2: using System.Reflection;
3:  
4: public class MyUnitTest : Attribute
5: {
6:     public MyUnitTest()
7:     {
8:    
9:     }
10: }
11:  
12: public class UnitTestFaildException : Exception
13: {
14: }
15:  
16: public class UnitTestHelper
17: {
18:     static public void Assert(bool statement)
19:     {
20:         if (!statement)
21:         {
22:             throw new UnitTestFaildException();
23:         }
24:     }
25: }
26:  
27: class Programm
28: {
29:     [MyUnitTest]
30:     public void AddTest1()
31:     {
32:         UnitTestHelper.Assert(Add(2, 2) == 4);
33:     }
34:  
35:     [MyUnitTest]
36:     public void AddTest2()
37:     {
38:         UnitTestHelper.Assert(Add(3, 2) == 5);
39:     }
40:  
41:     public int Add(int a, int b)
42:     {
43:         return 4;
44:     }
45: }
46:  
47: class Tester
48: {
49:     static void Main(string[] args)
50:     {
51:         Programm testClass = new Programm();
52:         Type type = testClass.GetType();
53:         
54:         // Durchlaufe alle Methoden der Klasse.
55:         foreach (MethodInfo mInfo in type.GetMethods())
56:         {  
59:             // Durchlaufe alle Attribute der Methode.
60:             foreach (Attribute attr in Attribute.GetCustomAttributes(mInfo))
61:             {
62:                 // Ist die Methode mit dem MyUnitTest Attribute gekennzeichnet?
63:                 if (attr.GetType() == typeof(MyUnitTest))
64:                 {
65:                     System.Console.WriteLine("Test " + mInfo.Name +
		    " wird gestartet");
66:  
67:                     try
68:                     {
69:                         mInfo.Invoke(testClass, null);
70:                         System.Console.WriteLine("-> Test war erfolgreich");
71:                     }
72:                     catch (System.Exception e)
73:                     {
74:                         System.Console.WriteLine("-> Test ist fehlgeschlagen");
75:                     }
76:                     
77:                 }
78:             }
79:  
80:         }
81:  
82:         System.Console.ReadLine();
83:     }
84: }

Ähnlich funktionieren die gängigen Unit Test Frameworks. Diese bieten meistens eigene und erweiterte Implementierungen von Assert-Methoden an und kommen häufig gleich mit einem PlugIn daher, mit dem alle Unit Tests automatisch durchlaufen werden und anschließend die Testergebnisse übersichtlich repräsentiert werden können. Gängige Unit Test Frameworks für .Net sind z. B. NUnit, MbUnit oder VSTS. Die Verwendung von NUnit will ich anhand eines einfachen Beispiels demonstrieren:

1: using System;
2: using System.Collections.Generic;
3: using System.Text;
4: using NUnit.Framework;
5:  
6: namespace Test.UnitTests
7: {
8:         [TestFixture]
9:         public class MyTest
10:         {
11:                 [Test]
12:                 public void TwoPlusTwo()
13:                 {
14:                         Assertion.AssertEquals(4, 2 + 2);
15:                 }
16:         }
17: }

Unter Visual Studio erweist sich das PlugIn TestDriven.Net im Zusammenhang mit NUnit als sehr hilfreich. Es ermöglicht die Ausführung von Unit Tests mithilfe weniger Klicks direkt aus der Entwicklungsumgebung.

Code Coverage als Metrik für Softwarequalität

Solche Unit Tests erweisen sich als besonders hilfreich bei der Sicherung der Softwarequalität. Softwarequaltität wird nach festgelegten Metriken beurteilt. Z. B. nach der Anzahl der fehlgeschlagenen bzw. erfolgreichen Unit Tests. In diesem Zusammenhang spielt aber auch eine andere interessante Frage eine wichtige Rolle: Wie groß ist die Testabdeckung? Diese Frage beantwortet das Tool NCover:

clip_image002[3]

Der NCoverExplorer zeigt detailliert an, welche Programmteile zu wie viel Prozent durch Unit Tests abgedeckt sind. Wie man sieht, ist das Projekt AdvertisementPlanner zu 9% durch Unit Tests abgedeckt. Die Kompontente SchedulingListbox ist zu 3% durch Unit Tests abgedeckt.

Als Metriken für die Softarequalität kann z. B. die Code Covarege/Test Cover herangezogen werden.

Im Umfeld der agilen Entwicklungsmethoden, z. B. dem Test Driven Development, wird der Begriff Code Coverage gleichgesetzt mit dem Begriff Test Covarge. Mit Test Covarge, zu Deutsch Testabdeckung, wird die Frage beantwortet, welche Codeteile durch Unit Tests abgedeckt sind und welche nicht, wie wir das im vorhergehenden Praxisbeispiel sehen konnten.

Eine Maßnahme zur Verbesserung der Softwarequalität könnte z. B. die Erhöhung der Testabdeckung des Projekts AdvertisementPlaner auf 20% sein. Dazu müsste erst einmal herausgefunden werden, welche Codepfade noch ungetestet sind. Der NCoverExplorer macht diese Aufgabe leicht. Jeder nicht getestet Codepfad wird einfach rot dargestellt:

clip_image004

Wie man sieht, wird die While-Schleife in der Methode AddAdvertisement nicht durch einen Unit Test abgedeckt.

clip_image006

Die Methode Connect wird noch gar nicht durch einen Unit Test erfasst.

NCover im Detail

Code Coverage ist nicht gleich Code Coverage. Für Code Coverage gibt es unterschiedliche Metriken. NCover unterstützt die Sequence Point Coverage (auch als Statement Coverage bezeichnet) und die Branch Coverage.

Sequence Points werden vom Compiler generiert und in den Debug Information Files (*.pdb) gespeichert. Im Regelfall entspricht ein Sequence Point einer einzelnen Anweisung in der verwendeten Hochsprache. Sequence Points werden normalerweise zum Debuggen verwendet. Jeder Sequence Point entspricht dabei einer Stelle im Progammcode, an der ein Breakpoint gesetzt werden kann. Neben der Information, ob ein Sequence Point durchlaufen wurde, liefert NCover auch noch die Anzahl der Durchläufe des entsprechenden Sequence Points:

clip_image002[5]

In der Abbildung kann man sehen, dass Zeile 17 zehnmal durchlaufen wurde. Zeile 23 (rot markiert) wurde gar nicht durchlaufen, da die If-Bedingung darüber nicht erfüllt ist. Die Sequence Point Covarage liefert das Verhältnis von allen vorhandenen Sequence Points zu allen durchlaufenen Sequence Points (im obigen Beispiel 92%).

Bei der Branch Coverage wird die Abdeckung des Programmcodes anhand des Kontrollflussgraphen ermittelt. Dabei werden nur Programmflussanweisungen berücksichtig. Betrachten wir folgende Beispiele:

clip_image004[4]

Eine lineare Abfolge von Anweisungen wird zu einem Knoten zusammengefasst. Für Verzweigunganweisungen wie z. B. in Szenario 1 werden zwei Nachfolgeknoten eingeführt. Der Knoten S1 entspricht dabei den Anweisungen, die ausgeführt werden, wenn die Bedingung (condition) erfüllt ist. Für die Szenarien 2 und 3 wurden ebenfalls die Kontrolflussgraphen dargestellt. Bei einer Branch Coverage wird das Verhältnis von allen vorhandenen Kanten zu allen durchlaufenen Kanten ermittelt.

Die erreichte Abdeckung durch Branch Covarge ist häufiger aussagekräftiger als die erreichte Abdeckung der Sequence Point Coverage, da lange lineare Sequenzen von Code weniger stark gewichtet werden als z. B. „kurze“ Behandlungen von Sonderfällen bzw. Fehlerbehandlungsroutinen.

Neben den von NCover unterstützten Metriken gibt es noch eine Reihe von anderen Metriken wie z. B. Decision Coverage, die aber an dieser Stelle nicht weiter ausführt werden sollen.

Fazit

Für jede Firma, die hauptsächlich .Net Anwendungen entwickelt und genaue Kennzahlen zur Testabdeckung haben oder wissen möchte, welche Codepfade am häufigsten durchlaufen werden, ist der Kauf von NCover empfehlenswert. Ich persönlich würde den Kauf der Enterprise Edition empfehlen – diese schlägt mit $299.00 zu Buche . Außerdem benötigt man im Regelfall nur eine Lizenz, da sich NCover leicht in einen vorhandenen Buildprozess integrieren lässt und sich damit die Ergebnisse der Code Coverage auch in einer HTML Ausgabe präsentieren lassen, die z. B. zentral über einen Server allen Entwicklern zugänglich gemacht werden kann.

von Vertexwahn, 01.07.2008 zugeordnet zu Entwicklersoftware , Erfahrungsberichte .

Kommentare

Es sind noch keine Kommentare vorhanden.

Eigener Kommentar

Sie müssen angemeldet sein, um ein Kommentar zu erstellen.
  • Schwierigkeit: Einsteiger
  • Views: 3424
  • Zur Druckversion
  • Artikel von Vertexwahn

Kick it on dotnet-kicks.de

Artikel

Autor

Kick it!

Wenn ihnen dieser Artikel gefällt, bitte "kicken" sie ihn.

WPF Forum | ASP.NET Forum | ASP.NET MVC Forum | Silverlight Forum | Windows Phone 7 Forum | SharePoint Forum | Dotnet Jobs | Dotnet Termine | Developer Blogs | Dotnet News

Das Team | Regeln | Impressum