HowTo: Asynchrone Methoden mit Unit Tests testen

imageIm letzten HowTo ging es um den Einstieg in die asynchrone Programmierung. Ein "Problem" dabei war, dass man nie genau weiß wie lange die Operation dauert. Mit einem kleinen Trick lassen sich auch solche Sachen elegant in einem UnitTest (oder Integrationstest) automatisiert testen.

Weiterlesen »

Code-Inside Casts: Einstieg in Unit Tests

Nach meinem letzten Screencast habe ich mich nun nochmal hingesetzt und einen neuen zum Thema “Einführung in Unit Tests” gemacht.

Erstmal möchte ich allerdings für das Feedback zum ersten Screencast danken. Ich habe diesmal wirklich versucht den “High Quality” Knopf bei YouTube zu bekommen (Video in einem ganz bestimmten Format hochladen – dann sollte das klappen), leider ging nicht. Allerdings werde ich da nochmal was versuchen.
Zudem habe ich vergessen das Camtasia Projekt File (ich mache die Screencasts mit Camtasia Studio 5) zu speichern und habe jetzt nur noch ein 90MB großes .MOV File, was ich nur ungern hier direkt hochladen möchte.
Im Übrigen spiele ich auch gerade mit dem Gedanken das vielleicht auf eine ganz andere Plattform zu schieben, aber nur hier erst mal das Video.

Achtung: Es soll sich in diesem 10 Minuten nur um den Einstieg handeln – also nicht zu viel erwarten ;)

Neue ASP.NET MVC Videos

Auf der offiziellen ASP.NET MVC Seite gibt es nun 15 neue Videos zu bestaunen:
- 10 HowDoI Videos
- 5 Pair Programming Videos mit dem Entwickler von NUnit

In den HowDoI Videos wird auf die Testbarkeit, sowie das generelle Konzept eingegangen – Routing, MVC usw.

Wer sich dafür interessiert, sollte mal einen Blick draufwerfen :)

HowTo: Eigene .NET Events definieren und mit Unit-Tests testen

In dem heutigen HowTo geht es um das Erstellen von eigenen .NET Events samt dem dazu gehörigen testen mit einem Unit-Test.

Was ist ein Event aus “Anfängersicht”?
Jeder der (wahrscheinlich) in einer X-beliebigen IDE für eine X-beliebige Sprache bereits irgendein Button auf ein Fenster gezogen hat, wird als Resultat dann so einen ähnlichen Code sehen:

        private void button1_Click(object sender, EventArgs e)
        {

        }

Hier kann man nun ganz genau definieren, was passiert wenn der “button1″ geklickt wird.
Eigentlich eine tolle Geschichte :)

Was passiert denn technisch im Grunde genommen dahinter?
Events sind von der Idee her bereits sehr alt. Der Grundgedanke ist einfach nach dem “Hollywood-Prinzip“: Ruf nicht uns an – wir rufen dich an.
Im Code brauchen wir nicht ständig prüfen ob der Button geklickt wird oder nicht – der Button sagt uns, wann er geklickt wird.
Ohne jetzt die .NET Implementierung (also was im Framework passiert) näher untersucht zu haben, würde ich meinen, dass das Grundkonzept aus dem Observer-Pattern abgeleitet ist.

Was ist ein Event: Beispiel aus der realen Welt
Um  mal ein Beispiel aus der realen Welt aufzugreifen – auch wenn dies manchmal arg abstrakt ist ;)
Wenn jemand ein “Bild-Zeitungs-Abo” hat, fragt der normal-deutsche-Leser auch nicht ständig den Herrn Springer ob es nun eine neue Ausgabe gibt oder nicht – er bekommt sein Exemplar automatisch sobald es gedruckt wurde.

Einsatzgebiet von Events
Sobald man irgendwelche “Prozesse” oder “Abläufe” modelliert, könnte man im Prinzip auf Events setzen – ich persönlich bin erst vor kurzem auf das Thema gekommen. Das liegt vor allem daran, dass ich bisher meistens mich mit Web-Projekten beschäftigt habe. In der ASP.NET Welt sind meiner Meinung nach Events nicht so unglaublich nützlich. Der Grund liegt auf dem, wie HTTP funktioniert:

image 
Der Client macht eine Anfrage und der Server antwortet entsprechend. Rein theoretisch geht nun ein Ablauf los – der irgendwann beeendet ist – das “Ich-bin-fertig-mit-meiner-Aufgabe” könnte über ein Event mitgeteilt werden.
An dieser Stelle sollte der Server an den Client zurückschicken: Bin fertig. Allerdings geht dies nicht:
image
Im HTTP Umfeld kann der Server nicht einfach Daten zum Client schicken – der Client  (Browser) muss immer erst die Anfrage stellen. Dadurch muss man auf der Clientseite (über AJAX z.B.) ein Polling durchführen. Das führt natürlich dazu, dass Events leicht nutzlos werden.
Allerdings sind sie in sämtlichen Client-Anwendungen die nicht auf HTTP beruhen äußerst nützlich :)

Eigene Events definieren
Dan Wahlin hat ein sehr schönes Video erstellt, in dem die Grundgedanken sehr gut vermittelt werden.

Mein Beispiel:
Wir haben eine bestimmte Anwendung, welche jeweils einen Verbindungsstatus haben kann:

image

Diese “ConnectionStates” in der Anwendung werden von einem “ConnectionManager” verwaltet.
Jetzt wäre es ja schön, wenn uns unser “ConnectionManager” sofort informiert, wenn sich der Status ändert.
Dazu erstmal das grobe Konstrukt unserer Klasse:

public class ConnectionManager
    {
        private ConnectionStates _state;

        public ConnectionStates State
        {
            get
            {
                return _state;

            }
            set
            {
                _state = value;
            }
        }

        public ConnectionManager()
        {
            this.State = ConnectionStates.Disconnected;
        }
    }

Der Anfangsstatus ist erstmal auf “Disconnected” gestellt. Der Rest sollte soweit klar sein.
Jetzt kommen wir zur eigentlichen Eventdeklaration.

Schritt 1: Delegat definieren
Als ersten Schritt schreiben wir uns ein Delegat:

public delegate void StateChangedEventHandler(object sender, StateChangedEventArgs e);

Ein Delegat darf man als eine Art “Funktionszeiger” (im Video von Dan Wahlin wird es auch als Pipe zwischen Objekten verglichen) verstehen.

Schritt 2: StateChangeEventArgs definieren
In unserem Delegat definieren wir eine Methodendeklaration die so später auch der Clientcode sieht – als EventArgs definieren wir ebenfalls unsere eigene Klasse:

    public class StateChangedEventArgs : EventArgs
    {
        public ConnectionStates NewConnectionStates { get; set; }
    }

Hier definieren wir einfach, was wir in unseren EventArgs später haben wollen – uns interessiert natürlich am meisten, was nun der neue Status ist.

Schritt 3: Event definieren
Jetzt definieren wir unser Event – an diesem können sich später die entsprechenden Clients melden:

public event StateChangedEventHandler StateChanged;

Dieses Event ist vom Typ “StateChangedEventHandler” – welches unser vorher definiertes Delegat ist.

Zwischenschritt: Der Client
Allein durch diese Definition des Events und des Delegates ist es möglich, das sich “Clientcode” an das Event dran hängt:

class Program
    {
        static void Main(string[] args)
        {
            ConnectionManager man = new ConnectionManager();
            Console.WriteLine("Start state: " + man.State.ToString());
            man.StateChanged += new ConnectionManager.StateChangedEventHandler(man_OnStateChanged);
            man.State = ConnectionStates.Connecting;
            man.State = ConnectionStates.Connected;
            man.State = ConnectionStates.Disconnected;
            Console.ReadLine();
        }

        static void man_OnStateChanged(object sender, StateChangedEventArgs e)
        {
            Console.WriteLine("State changed...");
            Console.WriteLine("New state is: " + e.NewConnectionStates.ToString());
        }
    }

Die “man_OnStateChanged” Methode ist zwar definiert – allerdings rufen wir in unserem “ConnectionManager” nie das Event auf.

Schritt 4: Event in der Klasse aufrufen
In unserem Setter müssen wir natürlich das Event werfen – dies geschieht über eine weitere Methode in der Klasse. Hier mal der komplette Source Code:

public class ConnectionManager
    {
        private ConnectionStates _state;

        public ConnectionStates State
        {
            get
            {
                return _state;

            }
            set
            {
                _state = value;
                OnStateChanged();
            }
        }

        public delegate void StateChangedEventHandler(object sender, StateChangedEventArgs e);

        public event StateChangedEventHandler StateChanged;

        protected void OnStateChanged()
        {
            if (StateChanged != null)
            {
                StateChangedEventArgs args = new StateChangedEventArgs();
                args.NewConnectionStates = this.State;
                StateChanged(this, args);
            }
        }
        public ConnectionManager()
        {
            this.State = ConnectionStates.Disconnected;
        }
    }

Bei jedem setzen eines ConnectionStates wird die “OnStateChanged” Methode aufgerufen – diese ist nur intern erreichbar (”protected”) bzw. von vererbten Klassen.
Diese Methode prüft, ob das “StateChanged” Event irgendwelche Beobachter hat – if(StateChanged != null).
Falls irgendwer im Clientcode sich an das Event angehangen hat, wird das Event mit unseren EventArgs geworfen.

Es klingt komplizierter als es ist
Da ich ebenfalls “neu” darin bin, musste ich mich ebenfalls erst mal in das Einarbeiten. Um es mal in kurzen Worten zu formulieren (soweit mein Verständnis richtig ist):

  • Am “event” StateChanged können sich beliebige Clienten anmelden. Der Clientcode hat die selbe Methodensignatur (object Sender, EventArgsXYZ args) wie in dem “delegat” definiert.
  • Das “delegat” ist nur eine definierte Schnittstelle zwischen den Objekten. Hier wird die Methodensignatur von dem Clientcode bestimmt.
  • Die “EventArgs” sind eigene Datenklassen um entsprechende sinnvolle Daten zu übermitteln wenn das Event geworfen wurde
  • Die interne “OnStateChanged” Methode prüft ob irgendwas am “event” hängt – wenn ja, dann löse es aus und leite es (über das delegat) an die richtige Stelle im Clientcode.

Resultat
In der Clientanwendung (die Consolen-Applikation) wird jedesmal die Ausgabe gemacht, sobald sich der Status ändert. Ohne jedes mal eine extra Methode aufzurufen oder die Ausgabe an den Manager zu ketten:

image

Unit-Tests: Wie teste ich Events?
Events kann man über eine nette C# 2.0 Sache testen: ein anonyme delegate. Den Trick habe ich bei Phil Haack gefunden.

[TestMethod]
        public void ConnectionManager_Raise_StateChanged_Event()
        {
            ConnectionManager man = new ConnectionManager();
            Assert.AreEqual(ConnectionStates.Disconnected, man.State);

            bool eventRaised = false;

            man.StateChanged += delegate(object sender, StateChangedEventArgs args)
            {
                eventRaised = true;
            };
            man.State = ConnectionStates.Connecting;

            Assert.IsTrue(eventRaised);
        }

In diesm Test lege ich einen bool “eventRaised” an – sobald das Event geworfen wird, wird ein anonymes delegat aufgerufen (man spart sich hier die zweite Methode) und ich setzt einfach diesen boolean auf “true”.
Sehr einfach und genial um zu testen, ob das Event wie gehofft auch geworfen wird :)

[Download Source Code]

HowTo: 3-Tier / 3-Schichten Architektur

Eine 3-schichtige Architektur ist eigentlich ein “Klassiker” in der Softwareentwicklung. Da allerdings das Thema sehr weitläufig ist und Anfänger (und unbelehrbare Entwickler) aus Mangel an Zeit, Lust oder Erfahrung zurückschrecken gibt es genügend Beispiele wo einfach darauf verzichtet wurde.

Um den Grundgedanken zu vermitteln und um zu zeigen, dass es eigentlich sehr einfach ist, sowas am Projektanfang zu implementieren, schreibe ich diesen Artikel

Was für “Schichten” und warum 3?
Fast jede Software greift auf Daten zurück – sei es XML, ein Webservice, eine Datenbank, eine Textdatei oder ein X-beliebiges anderes System.
Diese Daten werden irgendwie verarbeitet – sei es eine mathematische Funktion, eine Validierung oder eine bestimmte Filter und Suchfunktion.
Damit das Ergebnis auch irgendwo angezeigt wird (bzw. die Eingaben entgegen genommen werden) gibt es in den meisten Fällen auch ein Frontend, sei es eine Consolen-Applikation, eine Website oder irgend etwas anderes.

Jetzt wären wir bei den 3-Schichten angekommen:
 image

“Nur 3? Ich hab mehr!”
Natürlich kann man unzählige Schichten noch dazwischen schieben. Ein Beispiel ist z.B. die Software Factory von Microsoft. Da gibt es noch etliche Mappings zwischen den Data-Access-Schichten bis hin zu den Service-Schichten. Siehe auch den Wiki-Artikel zu den Schichtenmodellen.

“Ich frage nur Daten ab – ich brauch nur 2 Schichten.”
Über SQL etc. kann man natürlich auch Filtern, Sortieren etc. – da könnte man auch die “Business” Schicht in Frage stellen. Aus meiner Erfahrung sollte man das allerdings lieber nicht machen – später können noch irgendwelche Anforderung dazukommen, die nix im Data Access Layer zu suchen haben. Bis es soweit ist, könnte die Business-Schicht die Daten einfach nur “durchreichen”.

Beispielapplikation:
image

Data-Access Layer: “ThreeTier.Data”
Business Layer: “ThreeTier.Service”
Presentation Layer: “ThreeTier.ConsoleApp”
+ Unit-Tests: “ThreeTier.Tests”

Hier haben wir eine recht einfache Beispielapplikation – das ganze noch mit ein paar kleinen Unit-Tests bestückt (Einführung in Unit-Tests hier).

Die Architektur sieht man z.B. auch recht gut in Rob Conerys Storefront Projekt.

Schichten im Detail: ThreeTier.Data
image 
Hier definiere ich erst mal meine Objekte, welche ich im System nutze – simple POCOs.
Zugegeben, man kann sich darüber streiten ob man sein “Model” tatsächlich mit in dem Data-Projekt haben möchte. Da allerdings alles meistens mit irgendwelchen Daten zusammenhängt, passt das schon.
Wir haben hier nur die User Klasse:

image 
Im Ordern “DataAccess” liegt unsere Schnittstellen (Einführung zu Schnittstellen) zu den Datenquellen.
In diesem Fall haben wir nur die Schnittstelle “IUserRepository” (Repository zu dt. sowas wie Lager, Speicherort etc.) – dort definieren wir, welche Operationen ich generell auf eine X-beliebige Datenquelle ich ausführen möchte:
image
Das “DemoUserRepository” ist die konkrete Implementierung dieser Schnittstelle. Da ich keine DB oder ähnliches wollte, werden hier statische Daten zurückgegeben.

Welchen Vorteil bringt mir jetzt das Interface?
Das Interface könnte man hier in Frage stellen, allerdings erlaubt es später recht einfach die Datenquelle zu wechseln – weil alles auf der Schnittstelle beruht.
Da man im Regelfall mit einer DB etc. arbeitet möchte man z.B. in Unit-Tests nicht unbedingt die Datenbank fluten, sondern kann sich hier statische Testdaten zurückgegeben lassen. Einfach durch die Schnittstelle.
So könnte man auch leichter von einem “Showcase” zu einer echten Implementierung umschwenken.

Da ich in meinem Beispiel aber nur statische Daten zurückgebe, habe ich im Unit-Tests genau diese getestet.

Schichten im Detail: ThreeTier.Service

image
Im Service haben wir nach dem gleichen Prinzip auch eine Schnittstelle für unseren “UserService” erstellt.

image

In unserem UserService gibt es einmal eine Login-Methode und eine Methode, welche (ganz im Sinne von Social Networking) die Freunde von einem User zurück gibt. Hierbei habe ich zudem auch nur statische Daten genommen. Das ganze basiert allerdings auf dem “UserRepository”.

Schichten im Detail: ThreeTier.ConsoleApp
image

Mal wieder ein Konsolenprogramm – zwar ist das keine schöne Oberfläche, aber für das Beispiel soll es genügen:

        static void Main(string[] args)
        {
            Console.WriteLine("Great Social Community System - Please Login...");
            Console.Write("Name: ");
            string loginname = Console.ReadLine();
            Console.Write("PW: ");
            string password = Console.ReadLine();

            IUserService srv = new DemoUserService();

            if (srv.Login(loginname, password))
            {
                Console.WriteLine("Hello: " + loginname);
                Console.WriteLine("Your demo friend collection in the system: ");
                List<User> friends = srv.GetFriendsFromUser(loginname).ToList();

                foreach (User friend in friends)
                {
                    Console.WriteLine(" + " + friend.Login + " - Id: " + friend.Id);
                }
            }
            else
            {
                Console.WriteLine("Login failed");
            }

            Console.ReadLine();
        }

Ausgabe:

image 

Extras: Unit-Tests

Um ein gutes Beispiel zu geben, habe ich sogar 6 Unit-Tests geschrieben. Das Frontend hab ich allerdings nicht getestet ;)
image

Code-Coverage: 97% (Data + Service)
image

Resultat:

Durch die 3-Schichtige Architektur ist es später leichter Möglich neue Features einzubauen und die Applikation zu Warten. Im Team macht sich das auch recht gut, da man dadurch eine bessere Teamaufteilung machen kann.

[ Download Source Code ]

Video:

HowTo: Einfache Tests – UnitTests (oder keine Angst vor UnitTests…)

Einführung

Das Konzept der UnitTests (es gibt noch ein paar weitere Formen) ist bereits seit etlichen Jahren (oder Jahrzehnten?) bekannt. Es gibt viele Testframeworks für fast jede Sprache.
Im Visual Studio 2008 (jedenfalls der Professional/Team System Edition) sind UnitTests sehr einfach zu erstellen – und trotzdem hab ich erst vor kurzem die UnitTests für mich entdeckt. Trotz der Gründe für UnitTests und der Einfachheit kenn ich etliche Projekte, wo diese nicht existieren oder angewendet werden.
Die meisten Entwickler denken, dass UnitTests ziemlich komplex sind und eigentlich unnötig.
Die Gründe der Entwickler sind vielfältig (auch ich hab früher so oder so ähnlich gedacht ;) ) :

  • “Warum nicht einfach ein Konsolenprogramm erstellen oder per Debugger prüfen?”
  • “Ich seh es doch wenn eine bestimmte Komponente nicht funktioniert.”
  • “Für den extra Aufwand hab ich leider keine Zeit.”
  • “UnitTests klingt doch recht kompliziert, da ist mir die Einarbeitungszeit zu hoch.”

Ich bin kein Experte in UnitTests, allerdings haben sie mich bereits nach wenigen Minuten begeistert :)

UnitTests sind sehr schnell gemacht

(Achtung: Diese Aussage nicht auf die Goldwaage legen. Gute und durchdachte UnitTests sind keine leichte Aufgabe – darum gibt es ja z.B. auch eine extra Test Edition von Visual Studio wo sich)
Aber für den Anfang wollen wir die Behauptung mal so stehen lassen – Einfache Tests können sehr schnell durchgeführt werden)

Im Visual Studio 2008 wurde ein extra Template für Tests bereitgestellt:

image

Zudem kann man direkt in einem Projekt per Kontextmenü auf eine Klasse/Methode ein UnitTest erstellen:

image

Aber erst mal zur Grundfrage:

Warum sollte ich Tests machen?

Jeder Entwickler will (hoffentlich) gute und funktionstüchtige Software schreiben, die möglichst fehlerfrei ihren Dienst tut.
In der Zeit wo die Software entwickelt wird, werden sicherlich an vielen Ecken (oder Software-Schichten) Änderungen eingepflegt oder die Applikation muss erweitert werden. Insbesondere in einem Team oder wenn eine größere Umstellung ansteht (Datenbasis wechselt, Logik muss abgeändert werden) wird es kritisch: Laufen alle Komponenten noch wie erhofft?
Je größer die Anwendung, desto größer wird der Aufwand der Betrieben muss, um sicherzustellen, dass alles noch läuft.
Ein Test im UserInterface ist zwar machbar, ist allerdings meist sehr anstrengend und zeit intensiv (sollte natürlich auch gemacht werden).
Es wäre doch viel schöner, wenn die Tests automatisch erfolgen könnten – ohne viel Zeit mit Klicken zu verlieren – auch das die Tests jederzeit ausgeführt werden können wäre doch nett, oder?
Hier kommen die UnitTests: Genau sowas machen UnitTests (und noch mehr ;) ).

Stellen wir uns mal vor…

… dass wir eine nicht ganz triviale Software haben, welche verschiedene Layer (Data/Business etc.) hat. Die Software funktioniert gut – der Kunde ist zufrieden und als Entwickler fühlt man sich wohl.
Wie es meistens ist: Der Kunde möchte eine Änderung. Ein neues Attribut soll hier und da angefügt werden, eine Abfragelogik verändert werden und die Validation der Daten soll anders verlaufen.
Das Problem: Die Änderungen können viele Bereiche betreffen, sodass es leicht passieren kann, dass plötzlich garnichts mehr geht. Aber wo genau hakt es denn? Erstmal überall den Debugger ansetzen und nachverfolgen… hoffentlich übersicht man kein Fehler.
Ergebnis: An dieser Stelle ist es meist für den Entwickler ein etwas mulmiges Gefühl – wird die Software noch genauso funktionieren wie vorher (natürlich mit den Änderungen)?

… nun mal mit Tests vorstellen (ein Gedankenspiel) :

Die verschiedenen Methoden wurden während der Entwicklung der Version 1 bereits mit UnitTests getestet. Daten eintragen, löschen, verändern, laden, validieren, Fehler abfangen usw. – alle Aspekte die wichtig sind, wurden als Test hinterlegt.
Nun kommen die Änderungen: Es werden einige kritische Bereiche verändert, aber nach jeder Veränderung kann man automatisch alle Tests abspielen – schlägt der Test fehl, weiß man, wo Handlungsbedarf besteht. Die eben gemachte Änderung war wohl anscheinend nicht so gut.
Nach einer ganzen Weile: Die Tests werden wieder bestanden – das Herz des Entwicklers schlägt höher. Es können zwar immer noch Fehler auftreten (vielleicht muss ein neuer Test für einen neuen Aspekt noch hinzugefügt werden), aber die Grundzüge der Applikation stimmen noch.

Klingt doch eigentlich gut, aber wie sieht das in der Praxis aus:

Ein sehr (zugegeben) doofes Beispiel:

    public class DataManager
    {
        public bool ConnectToData()
        {
            return true;
        }

        public List<int> GetData()
        {
            return new List<int>() { 1, 2, 3, 4, 5, 6, 7 };
        }
    }

Unser DataManager kann sich zu einer beliebigen Datenquelle verbinden – in unserem Fall sagen wir einfach mal, dass die Verbindung geklappt hat.
Die GetData Methode gibt Daten zurück – in unserem Beispiel ein paar statische Daten.

Da sich die Datenabfrage-Logik ja ändern könnte und da auch die Datenquelle vielleicht sich noch ändert, implementieren wir lieber einen Test dafür:

Create Unit Test…

image 
Methoden auswählen, welche man testen möchte (beide in unserem Fall)…

image 
Name eingeben…

image 

Ein TestProjekt ist entstanden:

image

Generierter Test (dort steht eigentlich bereits das wichtigste drin) :

Visual Studio nutzt MSTest – das Test-Framework von Microsoft. Es ist ähnlich zu nUnit und co.

using DoNot.Fear.UnitTests.Data;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;

namespace DoNot.Fear.UnitTests.Test
{

    /// <summary>
    ///This is a test class for DataManagerTest and is intended
    ///to contain all DataManagerTest Unit Tests
    ///</summary>
    [TestClass()]
    public class DataManagerTest
    {

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //Use ClassCleanup to run code after all tests in a class have run
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //Use TestInitialize to run code before running each test
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //Use TestCleanup to run code after each test has run
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion

        /// <summary>
        ///A test for GetData
        ///</summary>
        [TestMethod()]
        public void GetDataTest()
        {
            DataManager target = new DataManager(); // TODO: Initialize to an appropriate value
            List<int> expected = null; // TODO: Initialize to an appropriate value
            List<int> actual;
            actual = target.GetData();
            Assert.AreEqual(expected, actual);
            Assert.Inconclusive("Verify the correctness of this test method.");
        }

        /// <summary>
        ///A test for ConnectToData
        ///</summary>
        [TestMethod()]
        public void ConnectToDataTest()
        {
            DataManager target = new DataManager(); // TODO: Initialize to an appropriate value
            bool expected = false; // TODO: Initialize to an appropriate value
            bool actual;
            actual = target.ConnectToData();
            Assert.AreEqual(expected, actual);
            Assert.Inconclusive("Verify the correctness of this test method.");
        }
    }
}

Die Kommentare und auch den TestContext kann man löschen – ich hab ihn bisher nicht gebraucht ;)
Achtung: Ich bin kein Experte in den Unit Tests – sondern ist nur eine Art Erfahrungsbericht :)

Machen wir doch erstmal einen einfachen Test ob die Verbindung klappt:

        [TestMethod()]
        public void DataManager_ConnectToData_IsTrue()
        {
            DataManager man = new DataManager();
            Assert.IsTrue(man.ConnectToData());
        }

Sehr schlicht, aber genau das was ich wissen muss. Der Name des Tests sollte ungefähr das beschreiben was er macht – damit man sich später noch zurechtfindet. In diesem Fall prüfe ich einfach, ob die Verbindung zustande kommt.
Die Assert-Klasse hat mehrere Methoden:

image

Jetzt können wir diesen Test durchlaufen und sehen:

image

Jetzt prüfen wir noch die andere Methode:

        [TestMethod()]
        public void DataManager_GetData_IsNotNull()
        {
            DataManager man = new DataManager();
            Assert.IsNotNull(man.GetData());
        }

        [TestMethod()]
        public void DataManager_GetData_CheckForZero()
        {
            DataManager man = new DataManager();
            List<int> result = man.GetData();
            foreach (int number in result)
            {
                Assert.AreNotEqual(0, number);
            }
        }

Die erste Methode prüft, ob überhaupt Werte zurückkommen. Mit dem zweiten Test wollte ich nur mal eine primitive Business-Logik Test machen (”kein Element darf 0 sein”).

Jetzt kann man alle Abspielen:

image

Ergebnis:

image

Schön, oder? :)

Ein Gedankenspiel:

Angenommen in unseren Daten schleicht sich tatsächlich eine 0 ein (Datenabfrage könnte zum Beispiel falsch sein oder es wurden falsche Daten eingetragen), dann schauen wir mal was passiert:

image

Ergebnis:

image

Fail!

image

Idealerweise sollten Tests möglich häufig (sie können sogar automatisch nach jedem Build laufen!) machen – um die Fehlerquelle einzugrenzen.
Angenommen wir haben bei uns einen Fehler in der Abfragelogik oder die Methode (die bei uns nicht existiert, aber existieren könnte) die Daten schreibt, war fehlerhaft oder die Validation fehlgeschlagen ist… (es kann ja viele Quelle geben).

Wir beheben also diesen Fehler (den wir vielleicht sonst nur sehr schlecht gefunden hätten), bis wir wieder das sehen:

image

Resultat beim Entwickler (& beim zufriedenen Kunden) :

image

Der Testcode:

[TestMethod()]
        public void DataManager_ConnectToData_IsTrue()
        {
            DataManager man = new DataManager();
            Assert.IsTrue(man.ConnectToData());
        }

        [TestMethod()]
        public void DataManager_GetData_IsNotNull()
        {
            DataManager man = new DataManager();
            Assert.IsNotNull(man.GetData());
        }

        [TestMethod()]
        public void DataManager_GetData_CheckForZero()
        {
            DataManager man = new DataManager();
            List<int> result = man.GetData();
            foreach (int number in result)
            {
                Assert.AreNotEqual(0, number);
            }
        }


Ergebnis:

Die Vorteile von UnitTests werden sicherlich erst nach und nach bei einem Projekt sichtbar, aber wenn man dies stetig fortführt, reduziert sich die Fehleranfälligkeit erheblich.
Das was ich hier gezeigt habe, ist sicherlich nicht das Ende der Fahnenstange – es gibt neben Unit Tests auch noch andere Tests. Das ist auf der MSDN Testing Seite recht gut beschrieben.

Test Driven Development (TDD) :

TDD beschreibt ein Entwicklungsstil, wo auf Tests besonders viel Wert gelegt wird. Hier werden die Tests immer vor der eigentlichen Implementation geschrieben. Man trifft seine Annahmen und da die Methode (oder die zu testende Komponente) ja noch keine Logik enthält, wird der Test erst fehlschlagen.
Nun geht es darum, den Test erfolgreich zu bestehen. Sobald dies geschafft ist, kann man die Implementation hinterher nochmal überarbeiten. (Refactoring). Nun kann man immer wieder prüfen, ob der Test noch funktioniert oder nicht – wenn er nicht mehr stimmt, dann haben wir wohl was falsch gemacht.
Am Ende haben wir (in der Theorie) jede Methode / Komponente mit Tests ausgestattet.

Unit Tests in ASP.NET MVC, Silverlight & co.:

Eine Klassenbibliothek lässt sich relativ leicht testen. In ASP.NET (WebForms) ist dies allerdings nicht ganz so leicht. In ASP.NET MVC wurde darauf ein besonderer Augenmerk gelegt.
Auch in Silverlight 2 wurde das Thema angegangen.

Weitere Links:

Wer nun etwas neugierig geworden ist, der kann sich auch diese Links anschauen:

Download:

Sample Code