Wie gesund ist eigentlich mein Code?

 

software quality metric: A function whose inputs are software data and whose output is a single numerical value that can be interpreted as the degree to which software possesses a given attribute that affects its quality.

 Definition nach IEEE Standard 1061 – [Quelle: Wikipedia]

Jede Software, deren Code länger als ein paar hundert Zeilen ist, wird irgendwann den Punkt erreichen, dass man den Code auf Anhieb nicht mehr verstehen kann. Die meisten von uns schreiben Code, der älter als ein paar Monate ist und noch ganz viele Jahre erhalten bleiben soll. (Alle, die das nicht wollen, können hier aufhören zu lesen).

Das Problem, das man früher oder später bekommt, ist die Komplexität unter Kontrolle zu halten. Jeder neuer Kollege hat das Problem, unbekannten, vorhandenen Code so schnell wie möglich zu verstehen. Für beide Fälle ist es sehr hilfreich, wenn man Tools zur Hand hat, die zum Beispiel die Zusammenhänge und Abhängigkeiten visualisieren können.

Als ich bei dem Open Space Karlsruhe die Frage gestellt habe, was die .NET-Community zu diesem Zweck nutzt,war die einstimmige Antwort : NDepend.  Code Metriken sind wichtig, sie sind aber nicht allmächtig. Wenn man allerdings wissen möchte, wie gesund sein Code ist, was sich verschlechtert hat und welche Baustellen aufgeräumt wurden, dann ist NDepend das de facto Standardtool, welches benutzt wird.

Was macht das Tool eigentlich?

Um all die Features zu beschreiben, die NDepend hat, würde man sehr viel Platz und Zeit benötigen – und zum Glück ist dies gar nicht nötig: auf deren Webseite findet man alles, was man braucht: Bilder, Erklärungen, weiterführende Links.

Ich würde hier nur zwei wichtige Funktionalitäten herausheben:

  • Visualisiert

MVC-Runtime Dependency Graph
Abhängigkeiten im MVC-Runtime

 

Auf diesem Bild sieht man, dass man gar nichts sieht 😀

Stellt euch mal vor, ihr müsstet ab sofort an MVC weiterentwickeln. Wo würdet ihr anfangen? Ich würde hiermit beginnen und immer mehr reinzoomen.

Alle Verwender von DotNetOpenAuth.OpenId

 

  • Erklärt

Das coolste für mich bei NDepend ist eigentlich nicht die Tatsache, dass es mir Statistiken und Grafiken liefert, sondern, dass es sie mir Diese auch  erklärt!

 

Interne Abhängigkeiten von DotNetOpenAuth.OpenId

 

Genau so läuft es auch mit den Metriken. Ich will nicht wissen, wie diese berechnet werden – eventuell später –  aber ich will wissen, was es bedeutet, wenn ein Wert zu hoch oder zu klein ist. Und das Tool erklärt dies alles oder leitet mich gezielt dahin weiter, wo es erklärt wird. Und so, ohne es zu merken, habe ich etwas gelernt, was meine Codequalität höchstwahrscheinlich erhöhen wird. Ich kann dadurch ein besserer Programmierer werden.

Es gibt noch sehr viele Gründe, wofür man NDepend ausprobieren bzw. nutzen sollte. Spätestens, wenn ein Team sich für gemeinsame Regeln einigen möchte, sollte man die Einhaltung durch Tools wie dieses und StyleCop and co. absichern. Dadurch wird irgendwann egal, wie ungesund unserer Code heute ist, morgen wird es ihm auf jedem Fall besser gehen – und uns auch.

Coding Dojo – der Trainingsraum für Entwickler

Definition: A Coding Dojo is a meeting where a bunch of coders get together to work on a programming challenge (the Code Kata). They are there to have fun and to engage in deliberate practice in order to improve their skills.

(Quelle: http://codingdojo.org/)

The Principles of a Coding Dojo
(extracted from the Laurent Bossavit’s Blog: bossavit.com/dojo/archives/2005_02.html)

  1. The First Rule
    One important rule about the Dojo is : At the Dojo one can’t discuss a form without code, and one can’t show code without tests. It is a design training place, where it is acknowledged that “the code is the design” and that code without tests simply doesn’t exist.
  2. Finding a Master
    The master can’t be a master of every forms. I feel quite at ease with recursive functions and list processing e.g. but I think I don’t know how to create even a simple web app. Fortunately, while it’s the first time they really deal with “tail-recursion” some practionners here have done professional web apps for years!
  3. Come Without Your Relics
    Of course, you know how to do it. You know how and why this code is better than that one. You’ve done it already. The point is to do it right now, explain it to us, and share what you learned.
  4. Learning Again
    In order to learn again something, we just have to forget it. But it’s not easy to forget something when you’re alone. It’s easier when we give our full attention to someone who just tries to learn it for the first time. We can learn from others mistakes as well as from ours if we listen carefully.
  5. Slow Down
    Learning something should force you to slow down. You can go faster because you learned some tricks, but you cannot go faster and learn at the same time. It’s OK, we’re not in a hurry. We could do that for years. Some of us certainly will. What kind of deadline will we miss if we spend four more weeks on this subject rather than on four different subjects? More precisely, when we reach the next plateau, is it because we went through the previous one, or is it just because we were flying over it?
  6. Throwing Yourself In
    At some time someone begins to master a subject and wants to approach another one. Those threatened by boredom should throw themselves first into a presentation. The goal is to get back to a good motivation level, ie. an acceptable level of difficulty.
  7. Subjecting To A Master
    If it seems difficult to you, look for other practitionners who can judge your code and could easily show something new about it to you. Ask again until the matter contains absolutely no more difficulty to you.
  8. Mastering A Subject
    If it seems easy to you, to explain it to other who find it difficult. Explain it again as long as they find it difficult.

(Quelle: http://codingdojo.org/)

Seit mehreren Monaten experimentieren wir mit Coding Dojos in der Firma. Nach jedem bisherigen Dojo haben wir etwas gelernt und diese Erkentnisse bei dem nächsten Session gleich angewendet: Wir haben sozusagen die Dojo-Finding-Kata gelöst und eine gute Lösung dafür gefunden, wie wir dabei das meiste lernen können.

Es gibt 2 Levels: für Anfänger und für weniger Anfänger, mit einem gemeinsamem Punkt: alle sind Softwareentwickler. Anfänger bedeutet: neu in der Welt der Tests und/oder neu in C#. Dadurch, dass wir sowohl C# als auch JavaScript-Dojos machen, werden wir uns mal den einen mal den anderen Schuch anziehen müssen ;). Durch diese Aufteilung sind wir in der Lage, eine passende Übung durchzuführen. Die Einladung geht an alle und es bleibt jedem selbst überlassen, zu entscheiden, bei welchem Level er/sie mitmachen möchte. Diese Selbst-Verpflichtung führt automatisch dazu, dass man es ernst.

Was die Form betrifft, haben wir mehrere Varianten ausprobiert:

  • Mit oder ohne Moderator: Erkenntnis: es muss einen Moderator geben, wenn man nicht will, dass der Abend in Frust und Streit endet 😉
  • mit Coder und Driver (Randori Modus): Erkenntnis: der Coder war gleichzeitig der Driver, der andere saß nur da und wartete auf seine ReiheThumbs-down-icon
  • mit Wechsel der Paare nach einer bestimmten Zeit: Erkenntnis: das mentale Ticken einer Uhr hat teilweise zu einer kompletten Blockade geführt. Warum soll man überhaupt einen Stopper verwenden? Niemand sagt einem bei der Arbeit: du hast 5 Minuten für diese Aufgabe sonst müssen wir die Firma schließen!!Thumbs-down-icon
  • ein Dojo für alle, unabhängig von Kenntnissstand: Erkenntnis: ein guter Entwickler, der in C# noch noch nicht so bewandert ist, hat genauso gute oder gar bessere Ideen, als die anderen bzw. braucht länger um den Syntax des Lösungsweges zu verstehen. Das kann dazu führen, dass man sich unterschätztThumbs-down-icon

Nach verschiedenen Diskussionen mit anderen Entwickler, die Dojos in Unternehmen erfolgreich etabliert haben (danke nochmal Ralf und Uli) und nach diesen eigenen Erfahrungen, haben wir uns für die folgende Variante entschieden:

  • die Tastatur geht herum, und nicht der Entwickler (das hat auch immer wieder zu Störungen geführt).
  • Jeder, der dran ist, muss entweder den allerersten Test schreiben oder den vorherigen lösen: das bedeutet, er muss den Code schreiben, wodurch der Test grün wird – nicht mehr und nicht weniger. Danach muss er noch den nächsten Test schreiben, und zwar so, dass er rot ist. (Es gab Fälle, wo die nächste Anforderung – also der nächste Test – gleich mit implementiert wurde.)
  • Es gibt keine zeitliche Begrenzung und niemand muss alleine grübeln. Wenn Fragen oder Mißverständnisse im Raum stehen, sie werden sofort geklärt. Die Fragen müssen trotzdem vom Entwickler formuliert werden: der Lernprozess ist immer da.
  • Der Weg ist das Ziel: wird eine Aufgabe nicht in der Zeit erfüllt, die uns zur Verfügung steht, dann ist das auch ein Erkenntniss, aus dem man lernen kann.

Thumbs-up-icon

Wir verfolgen durch diese Trainingsstunden verschiedene Ziele:
– lernen, wie man Tests schreibt, um eine nachhaltige Qualität abzusichern,
– lernen, wie man Features gegen Code-Veränderungen schützt.
– lernen, wie man eine Anforderung interpretiert,
– lernen, wie man eine Anforderung definiert,
– lernen, wie man eine konstruktive Diskussion führt um sich auf eine Lösung zu einigen,
– lernen, wie man SOLIDen Code schreibt, Abhängigkeiten erkennt und trennt, wie man für Menschen lesbaren Code schreibt.

Die Liste der Lerneffekten solcher Übungen ist sicher viel länger, aber vorerst reicht es, wenn wir das hier schaffen :). Diese Prozesse werden sich sicherlich ändern – genauso wie wir Entwickler. Die Grundlagen aber bleiben bestehen: alles, was wir hier lernen, soll sich – wird sich –  in der täglichen Arbeit wiederspiegeln und uns zu besseren Softwareentwickler und gleichzeitig zu besseren Teamplayer machen.

Regions or no regions – this is the question

Die dnc12 ist gerade vorbei und wir könnten uns schon wieder zusammensetzen 🙂
Heute gab es auf Twitter eine kurz angerissene Diskussion, die das Zeug dazu hatte, die Gemüter aufzuheizen: soll man oder soll man nicht #regions nutzen?

 

Nach dem kurzen Tweet-Austausch wurde es klar, dass es viele Entwickler gibt, die Regions gerne nutzen. Ich habe zwar den ganzen Abend nachgedacht, habe allerdings keine Gründe gefunden, sie selbst verwenden zu wollen.

 

Ich meine, warum sollte man Code NICHT sehen wollen?

  • Geht es vielleicht um eine oder mehrere Methoden, die man ausblenden will? Das würde aber entweder bedeuten, dass man
    – die Funktionalität der ganzen Klasse ausblendet, aber dann wozu, man öffnet einfach die Klasse nicht 😉
    – nur ein Teil der Funktionen ausblendet, und dann stellt sich die Frage, warum manche Funktionen viel öfter angeschaut werden als andere? Das hat für mich irgendwie ein CodeSmell
  • Geht es vielleicht um ein Teil einer einzigen Methode, und zwar einer ganz großen, sonst würde man sie nicht teilweise ausblenden wollen? Zusammengeklappt würde man dann eine Art Kommentar sehen, was mich sofort an Martin Fowlers Hinweis bezüglich Kommentare erinnert hat: Kommentare sind ideale Namensgeber. Wenn man im Code einen Kommentar braucht, dann ist das meistens ein Smell für ein Extract Method (genauso wie die Tatsache, dass die Methode wahrscheinlich zu lang ist)

    You have a code fragment that can be grouped together.

    Turn the fragment into a method whose name explains the purpose of the method.

  • Geht es vielleicht um Regionen um Methoden, Events, Fields, Properties, also nach Sichtbarkeit und Rolle? Dafür könnte ich einen einzigen Grund vorstellen, und zwar den, dass man auf Anhieb die öffentliche Methoden und Eigenschaften sehen will. Das wäre allerdings die Aufgabe eines Interfaces, oder? Dazu kommt auch noch meine – persönliche – Vorliebe, Code wie ein Buch zu lesen, von oben nach unten, also von einer öffentlichen Methode weiter in die Details, also zu den privaten Methoden (ganz nach CCD – Single Level of Abstraction (SLA))

    Hilfreich als Analogie ist der Blick auf Artikel in der Tageszeitung: dort steht zu oberst das Allerwichtigste, die Überschrift. Aus ihr sollte in groben Zügen hervorgehen, wovon der Artikel handelt. Im ersten Satz des Artikels wird dies auf einem hohen Abstraktionsniveau beschrieben. Je weiter man im Artikel fortschreitet, desto mehr Details tauchen auf. So können wir auch unseren Code strukturieren. Der Name der Klasse ist die Überschrift. Dann folgen die öffentlichen Methoden auf hohem Abstraktionsniveau. Diese rufen möglicherweise Methoden auf niedrigerem Niveau auf, bis zuletzt die “Bitpfriemelmethoden” übrig bleiben. Durch diese Einteilung kann ich als Leser der Klasse entscheiden, welchen Detaillierungsgrad ich mir ansehen möchte. Interessiert mich nur grob, wie die Klasse arbeitet, brauche ich mir nur die öffentlichen Methoden anzuschauen. In ihnen wird die Funktionalität auf einem hohen Abstraktionsniveau gelöst. Interessieren mich weitere Details, kann ich tiefer einsteigen und mir die privaten Methoden ansehen.

Was meint ihr, übersehe ich da was? (Notiz an mich: bei #nossued das Gespräch fortsetzen!)

Sind Unit Tests wirtschaftlich untragbar?

Immer wieder höre ich die Aussagen “Unit Tests sind schön und gut, wir haben nur keine Zeit dafür.” Oder “Klar, man kann Tests machen, hauptsache, es nimmt nicht zu viel Zeit von der Arbeit weg” ??!!

 

Nur um Missverständnisse zu vermeiden: Tests macht man nicht zum Spaß oder aus Langeweile, die Tests stellen die Essenz, die abstraktester Form der Lösung dar!

 

Ich habe bis heute Schwierigkeiten damit, meinem Gegenüber zu erklären, dass er sich irrt. Ich WEIß es einfach aus Erfahrung, dass dies eine Milchmädchenrechnung ist. Keine ernsthafte Argumente gegen Tests würden bei einer tieferen Überprüfung standhalten. Die Pros übertreffen klar die Kontras. Aber wie soll ich etwas – für mich – Offensichtliches in Worte fassen? Wie soll ich etwas in ein paar Sätzen erklären, was ich in einem andauernden Prozess durch jeden NICHT (oder nicht richtig) geschriebenen Test gelernt habe? Oder durch jeden Aha-Effekt oder durch jeden stressfreien Release (kein Stress entsteht, wo kein Platz für Bugs existiert 😉 )

 

Unit Tests sind für den Open Mind “selbsterklärend”: wenn der Bug in einem ungetesteten Code steckt, dann wird das zu einer “blinden” Fehlersuche führen, die Tage dauern kann und auf jedem Fall Geld und Ruf kostet. Wie lange dauert es, den Fehler in einem getesteten Codebasis zu finden, wo die Eingrenzung innerhalb von Sekunden erfolgt? Wie oft kommt es überhaupt vor, dass dieses Problem entsteht? Für mich schaut die Rechnung so aus:

 
Zeit_für_fehlersuche = Unproduktive_Zeit;
f(Unproduktive_Zeit) = Verschwendetes_Geld;
 
0->Zeit_für_fehlersuche(getesteter_Code)----------------------->Zeit_für_fehlersuche(ungetesteter_Code)---.....oo
 

Ok, ich glaube, ihr kennt jetzt meinen Standpunkt 😉 Aber ich bin ja nicht die ultimative Maßstab dafür, wie man arbeiten sollte. Deshalb habe ich ein paar Artikel und Statistiken von klügeren Leuten zusammengesucht, bitte liest die auch.

 

Diese Infos habe ich bei stackoverflow gefunden:

Realizing quality improvement through test driven development: results and experiences of four industrial teams und hier eine Diskussion darüber.

The study and its results were published in a paper entitled Realizing quality improvement through test driven development: results and experiences of four industrial teams, by Nagappan and research colleagues E. Michael Maximilien of the IBM Almaden Research Center; Thirumalesh Bhat, principal software-development lead at Microsoft; and Laurie Williams of North Carolina State University. What the research team found was that the TDD teams produced code that was 60 to 90 percent better in terms of defect density than non-TDD teams. They also discovered that TDD teams took longer to complete their projects—15 to 35 percent longer.

“Over a development cycle of 12 months, 35 percent is another four months, which is huge,” Nagappan says. “However, the tradeoff is that you reduce post-release maintenance costs significantly, since code quality is so much better. Again, these are decisions that managers have to make—where should they take the hit? But now, they actually have quantified data for making those decisions.”

Es gab auch kleinere Experimente dazu wie z.B. Code Lab – TDD vs. Non-TDD

Over 3 iterations, average time taken to complete the kata without TDD was 28m 40s. Average time with TDD was 25m 27s. Without TDD, on average I made 5.7 passes (delivering into acceptance testing). With TDD, on average I made 1.3 passes (in two attempts, they passed first time, in one it took 2 passes.)

Now, this was a baby experiment, of course. And not exactly laboratory conditions. But I note a couple of interesting things, all the same.

Und weil ein Bild mehr als tausend Worte spricht: Die Kostenverteilung bei getesteten Code schaut ungefähr so aus
Testing Benefits

Tests schreiben ist einfach, der Ertrag ist riesig. Warum soll man also keine Tests schreiben? Sind wir wirklich unfehlbar, schreiben wir immer den perfekten Code? Seien wir mal ehrlich…Ich bin es sicher nicht und ihr auch nicht.

 

Und hier noch die obligatorische Buchempfehlung: The Art Of Unit Testing Das Buch ist wunderbar verständlich geschrieben mit echten Beispielen und guten Argumenten, warum und wie man testen soll.

Webforms mit TDD entwickeln

Diese hier ist schon wieder eine wunderbare Idee aus Jimmy Nilssons Applying Domain-Driven Design and Patterns (das Buch scheint bis zur letzten Seite super Tipps zu liefern 😉 ) und zwar von Ingemar Lundberg.

Was ist eigentlich die Aufgabe einer Webseite: irgendwelche Controls mit Text zu füllen. Was dieser Text beinhaltet, dass wird von verschiedenen Funktionen entschieden. (Wie er ausgegeben wird, interessiert nicht.) Die Hauptaufgabe also bei der testgetriebenen Entwicklung von Webforms ist, diese Funktionalitäten zu ermitteln und zu implementieren. So bekommt man eine Webanwendung, bei der die Hauptbereiche getestet sind und nur die eigentliche Html-Ausgabe ungetestet bleibt. Außerdem wird auf dieser Art sichergestellt, dass die View sonst nichts tut.

Nehmen wir ein einfaches Beispiel: das Füllen eines Warenkorbs. Es stehen 3 Produkte zur Auswahl und der Käufer darf in seinen Warenkorb maximal 3 stellen. Um etwas Logik dabei zu haben, wird festgelegt, dass von ein Produkt nur maximal 2-mal gewählt werden darf. Wenn diese Bedingung erfüllt ist, soll das Produkt nicht mehr auswählbar sein. Gleiches gilt, wenn im Korb bereits 3 Produkte sind, kein Produkt darf mehr auswählbar sein.

Shopping Cart

Das Auswählen eines Produktes passiert z.B. mit einem OnClick-Event auf dem Link. Aus der Sicht der Funktionalität ist das nicht wichtig, hauptsache das Event wird ausgelöst.

Was tut also ein Modell um eine View zu steuern: nachdem es sichergestellt hat, dass alle Controls leer sind, lädt es die Daten mit irgendeiner Repository (nennen wir sie IDeposit), gibt sie der View und veranlasst diese, die Daten zu rendern. Danach muss es die übermittelten Daten identifizieren können und, wenn es OK ist, muss es diese mit einer anderen Repository (die nennen wir IAcquisition) abspeichern. Mit diesem “ist OK” wird sichergestellt, dass die obigen Regeln eingehalten wurden, also dass nicht zu viele Produkte bzw. identische Produkte ausgewählt wurden. Danach muss die View die Daten wieder rendern.

Mit diesen Informationen können wir bereits das Produkt und die 2 Interfaces definieren, die wir hier als Blackbox betrachten:

namespace WebformMVP.Tests
{
    //Wegen der Bedingung "nicht mehr als zwei vom selben Typ" muss eine Product-Klasse geben. Sonst würde auch ein string reichen
    public class Product
    {
        public string Name;
        public int Type;
        public Product(string name, int type)
        {
            Name = name;
            Type = type;
        }
    }

    public interface IDeposit
    {
        IList<Product> Load();
    }

    public interface IAcquisition
    {
        void Add(Product product);
    }
}

Jetzt ist endlich Zeit für den ersten Test. Wie ich schon am Anfang geschrieben habe, eine View muss einfach nur Text darstellen. Um die View simulieren zu können, wird sie von einem Interface abgeleitet, genauso wie die Testklasse, unsere Fakeview. Diese bekommt als Felder strings anstelle von Controls, die allerdings korrekt gefüllt werden müssen. Wir tun so als ob, wir abstrahieren die View auf das Minimum:

namespace WebformMVP.Tests
{
    public interface IShoppingView
    {
        void AddSourceItem(string text, bool available);
    }

    [TestFixture]
    public class Tests : IShoppingView
    {
        string m_sourcePanel;
        string m_shoppingCartPanel;

        [Test]
        public void FillSourcePanel()
        {
            m_model.Fill();
            m_model.Render();

            Assert.That(m_sourcePanel, Is.EqualTo("Product 1 available; Product 2 available; Product 3 available; "));
        }
    }
}

So wird die Anwendung natürlich nicht mal kompiliert :), dazu brauchen wir noch ein paar Schritte.

Dadurch, dass die Testklasse von diesem Interface ableitet, sind wir in der Lage, die Methoden entsprechend überschreiben zu können. Dieser Trick nennt sich Implement Interfaces Explicitly. Gleichzeitig lassen wir die Klasse auch von IDeposit ableiten, um auch dessen Methode zu überschreiben:

namespace WebformMVP.Tests
{
    [TestFixture]
    public class Tests : IShoppingView, IDeposit
    {
        string m_sourcePanel;
        string m_shoppingCart;
        IList<Product> m_sources= new List<Product>{new Product("Product 1", 1), new Product("Product 2", 2), new Product("Product 3", 3)};

        void IShoppingView.AddSourceItem(string text, bool available)
        {
            m_sourcePanel += text + (available ? " available;": string.Empty) + " ";
        }

        IList<Product> IDeposit.Load()
        {
            return m_sources;
        }

        [Test]
        public void FillSourcePanel()
        {
            m_model.Fill();
            m_model.Render();

            Assert.That(m_sourcePanel, Is.EqualTo("Product 1 available; Product 2 available; Product 3 available; "));
        }
    }
}

Es funktioniert immer noch nicht, wir brauchen ja noch ein Modell.

namespace WebformMVP.Tests
{
    public class ShoppingModel
    {
        public void Fill()
        {
            throw new NotImplementedException();
        }
        public void Render()
        {
            throw new NotImplementedException();
        }
    }

    [TestFixture]
    public class Tests : IShoppingView, IDeposit
    {
        ...
        private ShoppingModel m_model;

        //Es muss sichergestellt werden, dass beim Laden der View alle Felder leer sind.
        [SetUp]
        public void Setup()
        {
            m_sourcePanel = string.Empty;
            m_shoppingCart = string.Empty;
            m_model= new ShoppingModel();
        }

        [Test]
        public void FillSourcePanel()
        {
            m_model.Fill();
            m_model.Render();

            Assert.That(m_sourcePanel, Is.EqualTo("Product 1 available; Product 2 available; Product 3 available; "));
        }
    }
}

Ok, es kompiliert endlich! Aber wir gehen ja nach TDD vor, der Test ist wie gewünscht rot :D. Die 2 Methoden Fill und Render sind noch nicht implementiert.
Was sollen die Methoden tun? Fill() sollte eine lokale Liste mit Hilfe der Deposit-Repository füllen und Render() soll diese Elemente in das SourcePanel-Feld der View schreiben. Also muss unser Modell eine Liste, das IDeposit-Interface und das IShoppingVew als neue Member bekommen. Letzteren werden natürlich injectet (s. Dependency Inversion):

   public class ShoppingModel
   {
      IDeposit m_deposit;
      IList<Product> m_products;
      [NonSerialized]IShoppingView m_view;

      public ShoppingModel(IDeposit deposit)
      {
         m_deposit = deposit;
      }

      public void SetView( IShoppingView view )
      { 
         m_view = view;
      }
      public void Fill()
      {
         m_products = m_deposit.Load();
      }
      public void Render()
      {
         foreach( Product product in m_products )
         {
            m_view.AddSourceItem( product.Name, true );
         }
      }
   }

   [TestFixture]
   public class ShoppingCartTests:IShoppingView,IDeposit
   {
      ...
      private ShoppingModel m_model;

      [SetUp]
      public void Setup()
      {
         m_model = new ShoppingModel(this);
         m_model.SetView( this );
         m_sourcePanel = string.Empty;
         m_cartPanel = string.Empty;
      }

      [Test]
      public void FillSourcePanel()
      {
         m_model.Fill();
         m_model.Render();

         Assert.That( m_sourcePanel, Is.EqualTo( "Product 1 available; Product 2 available; Product 3 available; " ) );
      }
      ...
   }

Der Test ist grün! Jetzt ist sicher klar wie es weitergeht und ich will den Artikel nicht noch länger machen. Hier sind also die nächsten Tests und die Implementierung dazu:

   [TestFixture]
   public class ShoppingCartTests:IShoppingView,IDeposit
   {
      private string m_sourcePanel;
      private string m_cartPanel;
      private IList<Product> m_products = new List<Product> { new Product( "Product 1", 1 ), new Product( "Product 2", 2 ), new Product( "Product 3", 3 ) };
      private ShoppingModel m_model;

      [SetUp]
      public void Setup()
      {
         m_model = new ShoppingModel(this, new Cart());
         m_model.SetView( this );
         m_sourcePanel = string.Empty;
         m_cartPanel = string.Empty;
      }
...
      [Test]
      public void AddAnItem()
      {
         m_model.Fill();
         m_model.AddAt( 0 );
         m_model.Render();

         Assert.That( m_sourcePanel, Is.EqualTo( "Product 1 available; Product 2 available; Product 3 available; " ) );
         Assert.That( m_cartPanel, Is.EqualTo( "Product 1 " ) );
      }

      [Test]
      public void AddTwoItemsOfAKind()
      {
         m_model.Fill();
         m_model.AddAt( 0 );
         m_model.AddAt( 0 );
         m_model.Render();

         Assert.That( m_sourcePanel, Is.EqualTo( "Product 1 Product 2 available; Product 3 available; " ) );
         Assert.That( m_cartPanel, Is.EqualTo( "Product 1 Product 1 " ) );
      }

      [Test]
      public void AddThreeDifferentItems()
      {
         m_model.Fill();
         m_model.AddAt( 0 );
         m_model.AddAt( 2 );
         m_model.AddAt( 1 );
         m_model.Render();

         Assert.That( m_sourcePanel, Is.EqualTo( "Product 1 Product 2 Product 3 " ) );
         Assert.That( m_cartPanel, Is.EqualTo( "Product 1 Product 3 Product 2 " ) );
      }
...
      void IShoppingView.AddCartItem( string text )
      {
         m_cartPanel += text + " ";
      }
   }
   public class ShoppingModel
   {
      IDeposit m_deposit;
      IList<Product> m_products;
      ICart m_cart;
      [NonSerialized] IShoppingView m_view;

      public ShoppingModel(IDeposit deposit, ICart cart)
      {
         m_deposit = deposit;
         m_cart = cart;
         m_view = view;
         m_products = new List<Product>();
      }
      public void SetView( IShoppingView view )
      { 
         m_view = view;
      }

      public void Fill()
      {
         m_products = m_deposit.Load();
      }

      public void Render()
      {
         foreach( Product product in m_products )
         {
            m_view.AddSourceItem( product.Name, m_cart.IsOkToAdd(product) );
         }
         foreach( Product product in m_cart.List )
         {
            m_view.AddCartItem( product.Name );
         }
      }

      public void AddAt( int index )
      {
         var product = m_products[index];
         m_cart.Add( product );
      }
   }

   public interface ICart
   {
      bool IsOkToAdd( Product product );
      void Add( Product product );
      IList<Product> List { get; }
   }

   public class Cart :ICart{

      private IList<Product> m_cartItems = new List<Product>();

      public bool IsOkToAdd( Product product )
      {
         return m_cartItems.Where( a => a.Type == product.Type ).Count() < 2 && m_cartItems.Count < 3;
      }

      public void Add( Product product )
      {
         m_cartItems.Add( product );
      }

      public IList<Product> List
      {
         get { return m_cartItems; }
      }
   }

Die einzige größere Änderung zum ersten Test ist das neue ICart-Objekt. Da es hier um mehr als es eine Liste geht (irgendwo muss ja die Logik der maximal 2 gleichen Produkte pro Warenkorb errechnet werden), habe ich dafür das Interface und die Klasse definiert.

Jetzt sind wir fast fertig. Es muss lediglich die abstrahierte Umgebung in eine Webanwendung nachgebaut werden. Das heißt, wir implementieren die Methoden Page_Load(), Pre_Render() und AddAt_Click() und die Methoden des Interface IShoppingView. Um die Kontrolle zu behalten, löschen wir den Designer und schalten den ViewState aus (deswegen mag ich diesen Ingemar so sehr 😉 ).

//default.aspx
<%@ Page Language="C#" AutoEventWireup="true" EnableViewState="false" CodeBehind="Default.aspx.cs" Inherits="ShoppingCart.Web._Default" %>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Shopping Cart</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:Panel runat="server" ID="srcPanel"></asp:Panel>
    <asp:Panel runat="server" ID="cartPanel"></asp:Panel>
    </div>
    </form>
</body>
</html>

//default.aspx.cs
using System;
using System.Collections.Generic;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

namespace ShoppingCart.Web
{
   public class _Default : System.Web.UI.Page, IShoppingView
   {
      protected ShoppingModel model;
      protected HtmlTable srcTable, cartTable;
      protected Panel srcPanel, cartPanel;

      protected void Page_Load( object sender, EventArgs e )
      {
         if( !IsPostBack )
         {
            model = new ShoppingModel(new FakeDeposit(),new Cart());
            //Speichern, hier in Session aber sonst natürlich mit einer Repository
            Session["ShoppingModel"] = model;
            model.Fill();
         }
         else
         {
            model = (ShoppingModel)Session["ShoppingModel"];
         }
         model.SetView( this );
         ModelRender();
      }

      protected void Page_PreRender()
      {
         srcPanel.Controls.Clear();
         cartPanel.Controls.Clear();
         ModelRender();
      }

      private void ModelRender()
      {
         srcTable = new HtmlTable();
         srcPanel.Controls.Add( srcTable );
         srcTable.Width = "50%";
         srcTable.Border = 1;

         cartTable = new HtmlTable();
         cartPanel.Controls.Add( cartTable );
         cartTable.Width = "50%";
         cartTable.Border = 1;
         cartTable.BgColor = "#cccccc";

         model.Render();
      }
      public void AddSourceItem( string text, bool available )
      {
         int index = srcTable.Rows.Count;
         HtmlTableRow tr = new HtmlTableRow();
         HtmlTableCell tc = new HtmlTableCell { InnerText = text };
         if( available )
         {
            LinkButton lb = new LinkButton();
            tc.Controls.Add( lb );
            lb.ID = index.ToString();
            lb.Text = ">>";
            lb.Click += AddAt_Click;
         }
         tr.Cells.Add( tc );
         srcTable.Rows.Add( tr );
      }
      private void AddAt_Click( object sender, EventArgs e )
      {
         model.AddAt( Convert.ToInt32( ((LinkButton)sender).ID ) );
      }
      public void AddCartItem( string text )
      {
         HtmlTableCell tc = new HtmlTableCell { InnerText = text };
         HtmlTableRow tr = new HtmlTableRow();
         tr.Cells.Add( tc );
         cartTable.Rows.Add( tr );
      }
   }

   internal class FakeDeposit :IDeposit
   {
      public IList<Product> Load()
      {
         return new List<Product> { new Product( "Product 1", 1 ), new Product( "Product 2", 2 ), new Product( "Product 3", 3 ) };
      }
   }
}

Fertig. Ich muss eingestehen, als ich das Beispiel aus dem Buch nachprogrammiert habe, war ich wirklich überrascht, wie alles geklappt hat, obwohl ich während des Testens keine Webseite angesprochen habe. Die Wahrheit ist, ich habe noch nie nach dem MVP-Pattern entwickelt, aber eine Webseite so aufzusetzen ist genial! Hoch lebe die Abstraktion!

Ich lade hier das Projekt hoch, vielleicht glaubt es mir jemand nicht 😉

Specification Pattern

Seitdem ich die praktische Anwendung des von Eric Evans in Domain-Driven Design beschriebenen Spezifikationsmusters in Jimmy Nilsons Buch gesehen habe, bin ich ein großer Fan geworden.

SPECIFICATION provides a concise way of expressing certain kinds of rules, extricating them from conditional logic and making them explicit in the model.

Genau dasselbe Prinzip habe ich in diesem Artikel angewendet und ich nutze dieser Vorgehensweise laufend.

  • Wann braucht man eine Spezifikation?
    Immer, wenn ein Domain-Objekt bestimmte Erwartungen erfüllen muss, zum Beispiel beim Validieren (wie in o.g. Artikel) oder bei der Suche anhand von bestimmten Kriterien.
  • namespace Specification
    {
        public interface IValidable{}
    
        public class Invoice : IValidable{}
    
        public interface ISpecification
        {
            bool IsSatisfiedBy(IValidable obj);
        }
    
        public class BlackListClientsSpecification : ISpecification
        {
            private readonly DateTime m_currentDate;
    
            public DelinquentClientsSpecification(DateTime currentDate)
            {
                m_currentDate = currentDate;
            }
    
            public bool IsSatisfiedBy(IValidable obj)
            {
                Client candidate = obj as Client;
                //do something with Candidate and CurrentDate
                return true;
            }
        }
    }
    //Selektion:
    ...
        public class ClientRepository
        {
            IList<Client> SelectSatisfying(ISpecification specification)
            {
                return SelectAllClients().Where(a => specification.IsSatisfiedBy(a));
            }
        }
    ...
        var delinquentClients = clientRepository.SelectSatisfying(new DelinquentClientSpecification(currentDate));
    ...
    
  • Wozu braucht man eine Spezifikation?
    Ich kenne das aus eigener Erfahrung, wie sich Codeschnippsel (zum Beispiel obj.State==valid oder obj.Activ==true && obj.Created usw.), die verschiedenen Kriterien darstellen, wie Unkraut überall in Code verbreiten. Das kann man mit dieser Lösung wunderbar unterbinden, alle Bedingungen müssen in die jeweils passende Spezifikation gesammelt werden.

Man kann als Basis ein Interface, ( ISpecification ) oder eine abstrakte, an das beschriebene Domain Objekt angepasste Klasse InvoiceSpecification nutzen, abhängig davon, ob man nur einen Kontrakt oder auch Code wiederverwenden will.
Aus der Sicht des Testens, sind natürlich diese Klassen ideal, sie haben keine Abhängigkeiten, sind pure logische Ausdrücke der Entscheidungen z.B. der Geschäftsleitung. Für diese Klassen wurde TDD erfunden ;).

Coding Dojo – aber wie?

Wir haben in der Abteilung vor ein paar Wochen eingeführt, am letzten Tag von jedem Sprint einen kleinen Firmeninternen Dojo abzuhalten. Da wir vom Anfang an zu den Münchener Coding Dojos gehen, haben dadurch ziemlich viel Erfahrung gesammelt, wie so ein Dojo durchgeführt wird.

Wie laufen üblicherweise diese Veranstaltungen ab? Der Organisator beschreibt die zur Auswahl stehenden Aufgaben, die Teilnehmer besprechen es kurz, und nachdem die Wahl getroffen wurde, wird fast sofort losgelegt. An der Tastatur. Der erste Test entsteht dann allerdings erst nach 30-40 Minuten. Geplant wird mit einem zuckenden Finger über die Tastatur, der Test entsteht oft nach einem frustrierten Ruf “schreib endlich was, egal was!”. Am Ende jedes Dojos hatten wir dann die Anforderungen erfüllt, aber zur Refactoring kamen wir nie.

Aber wir sind Entwickler, wir entwickeln uns ständig weiter. Seit längerer Zeit spürten wir, dass wir so einen Prozess anders steuern sollten, dass etwas fehlt.

An diesem Freitag haben wir also etwas anderes ausprobiert, sozusagen à la Clean Code: zuerst Anforderungen aufschreiben (das war ja nichts neues), dann nachdenken. Ohne Tastatur, nur am Flipchart. Das war neu. Wir haben die ganze Planung inklusive Funktionseinheiten, Methodennamen, Abhängigkeiten aufgezeichnet.
Danach ging erst das Entwickeln mit TDD los. Nach ca. 90 Minuten waren wir fast fertig und wir haben keine Refactoring mehr gebraucht.

Auf die Frage nach der Meinung den Kollegen, ob das jetzt besser gelaufen war, waren die Antworten geteilt: manche meinten, wir wären auch auf dem anderen Weg zum selben Ergebnis gekommen. Die Wahrheit ist, das Beispiel war recht einfach, es gab nicht sehr viele trennbare Verantwortlichkeiten, aber ich bin sicher, dass man auf dieser Art viel mehr lernen kann. Es reicht ja nicht, eine Sprache zu kennen, ein guter Entwickler muss auch Abstraktionsebenen erkennen und definieren können. Ich finde diese Art zu Arbeiten: zuerst alle Möglichkeiten auf einem Stück Papier zu bewerten und dann sich auf die einzelne Methoden zu konzentrieren, viel effektiver, ich glaube fest daran, dass es zu besserem Code führt.

Also die Frage ist: wie soll eine mit TDD entwickelte Anwendung entstehen? Vom Anfang mit der Tastatur unter den Fingern oder darf es etwas Planung in den Köpfen und auf Papier stattfinden? Haben wir die Regeln von TDD verletzt oder verbessert?

SOLID Principles

Im vorherigen Artikel habe ich drei der fünf wichtigsten Prinzipien des OOD (Objektorientiertes Design) benannt, ohne mehr darüber zu schreiben. Das würde ich jetzt gerne nachholen.
Diese Prinzipien wurden von Robert C.Martin (a.k.a. Uncle Bob) unter dem Namen S.O.L.I.D. Principles zusammengefasst:

  • SRP: The Single Responsibility Principle
  • OCP: The Open Closed Principle
  • LSP: The Liskov Substitution Principle
  • DIP: The Dependency Inversion Principle
  • ISP: The Interface Segregation Principle

Das Thema wurde von Uncle Bob in mehreren Blogartikeln, Podcasts und vor allem in seinem Buch sehr ausführlich erklärt. Auch andere Entwickler haben darüber geschrieben, zum Beispiel hat Stefan Lieser darüber eine ganze Artikelserie in dotnetpro veröffentlicht.
Ich habe das erste Mal vor einem Jahr in diesem Podcast von Hanselman und Uncle Bob darüber gehört, und während der letzten 12 Monaten wurde ich durch die tägliche Arbeit überzeugt, dass man mit diesen Regeln solide Anwendungen bauen kann.

Was bedeuten also diese Akronyme:

SRP: The Single Responsibility Principle

Die Definition lautet:

A class should have only one reason to change.

Diese Regel ist wahrscheinlich die einfachste und wird wahrscheinlich am meisten verletzt. Wer kennt nicht Klassen, die sowas tun, wie Daten speichern, manipulieren, E-Mails versenden und all das eventuell auch noch loggen. Das war früher eine ganz normale Vorgehensweise. Was passiert aber, wenn die Datenbank-Struktur sich verändert hat? Dann musste man nicht nur diese ändern sondern auch all die Klassen – meistens Verwaltungen oder Manager genannt – die all diese Verantwortlichkeiten hatten. Und darum geht es hier: eine Klasse darf nur einen Grund für Änderungen haben, sie darf nur eine Verantwortlichkeit haben. Also wenn man mehrere Gründe erkennen kann, warum sich eine Klasse verändert, dann wurde dieses Prinzip verletzt. Und das gilt nicht nur für Klassen, sondern auch für andere Funktionseinheiten wie Funktionen, Klassen, Assemblies, Komponenten, alle auf ihre Abstraktionsebene betrachtet.

OCP: The Open Closed Principle

Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.

Jede Funktionseinheit soll erweiterbar sein, also darf nicht zu viele Abhängigkeiten haben, weil die diese Freiheit stark oder ganz einschränken können. Wenn man allerdings ein verändertes Verhalten implementieren will, soll das nicht durch Veränderung des Codes sondern durch hinzufügen von neuen Funktionen passieren.
Das ist nur durch ausreichende Abstraktion zu erreichen. Wenn die Kernfunktionalität in eine abstrakte Basisklasse gekapselt ist, kann man das neue Verhalten in einer abgeleiteten Klasse implementieren.

LSP: The Liskov Substitution Principle

Dieses Prinzip wurde von Barbara Liskov definiert und es lautet so:

Subtypes must be substitutable for their base types.

Einfach übersetzt: jede abgeleitete Klasse einer Basisklasse muss diese Klasse so implementieren, dass sie durch diese jeder Zeit ersetzbar ist. Jedes Mal, wenn man eine Basisfunktion so implementiert, dass diese was ganz anderes tut, als man grundsätzlich erwarten würde, verletzt man dieses Prinzip. Das berühmteste Beispiel ist das Rechteck und das Quadrat. Auf den ersten Blick meint man, dass ein Quadrat ein spezialisiertes Rechteck ist. Was passiert aber, wenn man die Länge oder die Breite eines Quadrates setzt? Es muss jeweils die andere Eigenschaft auch gesetzt werden, sonst wäre es ja kein Quadrat ;). Das ist aber ein Verhalten, was man bei einem Rechteck niemals erwarten würde. Also würde diese Ableitung das Liskovsche Substitutionsprinzip grob verletzen.

DIP: The Dependency Inversion Principle

Die Definition lautet

1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
2. Abstractions should not depend upon details. Details should depend upon abstractions

Dieses Prinzip ist sehr einfach zu erklären (Beispiel für die Verletzung sieht man ja im vorherigen Artikel): Keine Klasse sollte fremde Klassen instanziieren, sondern diese als Abstraktionen (z.B. Interfaces) in Form eines Parameters bekommen. Das führt dazu, dass die fremde Klasse als Black Box fungieren kann, ihre Veränderungen würden nicht zu Veränderung dieser konkreten Klasse führen.

ISP: The Interface Segregation Principle

Das Prinzip bezieht sich auf “fette” Interfaces:

Clients should not be forced to depend on methods they do not use.

Ein Interface ist der veröffentlichte Kontrakt einer Klasse, eines Moduls. Je mehr Methoden darin registriert wurden, vor allem, wenn sie sehr ähnlich sind oder wenn sie keine selbsterklärende Namen haben, dann ist das eine Zumutung gegenüber der Clients, des Verwenders. Er könnte dazu gezwungen sein, den Code der dahinter stehenden Implementierung anzusehen, was die Erstellung des Interfaces sinnlos macht. Dieses soll ja als Black Box fungieren, nicht als eine Herausforderung für den Entwickler.

Jeder Code ist testbar

Immer wieder steht man vor der Herausforderung, zu einem bestehenden Legacy-Code neue Funktionalitäten hinzu zufügen oder vorhandene Bugs beheben zu müssen.
Ich möchte hier eine Möglichkeit dazu beschreiben, einen Weg den ich u.a. von Michael Feathers gelernt habe.

Nehmen wir an, der Code schaut so aus:

using System.Net.Mail;

namespace LegacyCode
{
   public class KompletterWorkflow
   {
      public void SpeichereDatenUndVersendeMail( int id, string emailaddress, string text, string cc )
      {
         if( !string.IsNullOrEmpty( text ) )
         {
            Daten user;
            DatenRepository repository = new DatenRepository();
            user = new Daten { Id = id, Email = emailaddress };
            repository.Save( user );

            if( !SmtpRepository.IsValidEmailAddress( emailaddress ) ) return;
            IEmailRepository emailrep = new SmtpRepository();
            MailMessage message = new MailMessage( "myAddress@xy.com", emailaddress, "Testmail", text );
            if( !string.IsNullOrEmpty( cc ) ) message.CC.Add( cc );
            emailrep.SendMail( message );
         }
      }
   }
}

Die referenzierten Klassen wären dann sowas wie:

   public class DatenRepository
   {
      public void Save( Daten daten )
      {
         //Speichert die Daten ab
      }
   }

   public interface IEmailRepository
   {
      void SendMail( MailMessage message );
   }

   public class SmtpRepository : IEmailRepository
   {
      public static bool IsValidEmailAddress( string address )
      {
         try
         {
            MailAddress mailAddress = new MailAddress( address );
            return true;
         }
         catch
         {
            return false;
         }
      }

      public void SendMail( MailMessage message )
      {
         //Versende die Email
      }
   }

   public class Daten
   {
      public int Id { get; set; }
      public string Email { get; set; }
   }

Diese Klasse, so wie sie ist, verletzt jede Menge Prinzipien.
Die wichtigsten sind: The Single Responsibility Principle (SRP) und The Open-Closed Principle (OCP). Außerdem ist die Klasse in diesem Zustand nicht testbar, da man mittendrin Objekte erstellt, deren Verhalten auch mitgetestet werden müssten.
Wenn man das weißt, dann ist die Aufgabe einfach: die Implementierungen DatenRepository und SmtpRepository dürfen nicht in der Methode instanziert werden, sondern sie müssen nach der Regeln der Inversion of Control (IoC) der Klasse übergeben werden.
Zusätzlich werden auch die zusammenhängenden Parameter der Methode SpeichereDatenUndVersendeMail zum userDaten zusammengefasst.

   public class KompletterWorkflow
   {
      private DatenRepository m_repository;
      private IEmailRepository m_emailrep;

      public KompletterWorkflow(DatenRepository repository, IEmailRepository emailrep)
      {
         m_repository = repository;
         m_emailrep = emailrep;
      }

      public void SpeichereDatenUndVersendeMail( Daten userDaten, string text, string cc )
      {
         if( !string.IsNullOrEmpty( text ) )
         {
            m_repository.Save( userDaten);

            if( !SmtpRepository.IsValidEmailAddress( userDaten.Email ) ) return;
            MailMessage message = new MailMessage( "myAddress@xy.com", userDaten.Email, "Testmail", text );
            if( SmtpRepository.IsValidEmailAddress( cc ) ) message.CC.Add( cc );
            m_emailrep.SendMail( message );
         }
      }
   }

Um SRP gerecht zu werden, müsste man auch die Methode in 2 teilen: DatenSpeichern und VersendeMail. Das ist allerdings aus der Sicht der Testbarkeit unwichtig und ich will den Artikel nicht unnötig in die Länge ziehen 😉

Jetzt müssen wir nur noch die Tests schreiben.
Da wir Unittests schreiben, also nur das Verhalten dieser eine Methode testen, müssen wir die fremden Objekte mocken: Das hbedeutet, wir werden ihr Verhalten nachspielen, so tun als ob.

Wir müssen 3 verschiedene Fälle behandeln: ein Interface IEmailRepository, eine konkrete Implementierung DatenRepository und eine statische Methode SmtpRepository.IsValidEmailAddress(string). Bei den ersten zwei empfehlt es sich ein Mocking-Framework wie z.B. RhinoMock zu nutzen.

using NUnit.Framework;
using Rhino.Mocks;
using System.Net.Mail;
namespace LegacyCode.Tests
{
   [TestFixture]
   public class DatenSpeichernUndVersendeMailTests
   {
      private IEmailRepository m_emailrep;
      private DatenRepository m_repository;

      [SetUp]
      public void Init()
      {
         m_emailrep = MockRepository.GenerateStub<IEmailRepository>();
         m_repository = MockRepository.GenerateStub<DatenRepository>();
      }

      [Test]
      public void Daten_werden_gespeichert_wenn_Text_nicht_leer()
      {
         //Arrange
         KompletterWorkflow workflow = new KompletterWorkflow( m_repository, m_emailrep );
         Daten userDaten = new Daten{Id=1,Email="test@test.de"};

         //Act
         workflow.SpeichereDatenUndVersendeMail( userDaten , "Emailtext", "cc" );

         //Assert
         m_repository.AssertWasCalled( a => a.Save( userDaten ) );
         m_emailrep.AssertWasCalled( a => a.SendMail( Arg<MailMessage>.Matches( b => b.To[0].Address == userDaten.Email && b.CC.Count == 0 ) ) );
      }
   }
}

Wenn wir jetzt die Tests mit NUnit ausführen, bekommen wir folgende Fehlermeldung:

LegacyCode.Tests.DatenSpeichernUndVersendeMailTests.Daten_werden_gespeichert_wenn_Text_nicht_leer:
System.InvalidOperationException : No expectations were setup to be verified, ensure that the method call in the action is a virtual (C#) / overridable (VB.Net) method call

Das heißt, die Methoden, worüber wir Annahmen treffen, müssen entweder in einem Interface veröffentlicht worden oder überschreibbar also virtual sein.

      public virtual void Save( Daten daten )
      {
         //Speichert die Daten ab
      }

Wenn wir jetzt die Tests durchführen, dann passt alles.

Für die statische Methode erstellt man am besten eine separate Testklasse. Da hier keine andere Komponenten wie Datenbank oder Filesystem angesprochen werden, wird die Methode ganz einfach zu testen sein:

   [TestFixture]
   public class IsValidEmailTests
   {
      [Test]
      public void Leere_Adresse_ist_nicht_valide()
      {
         Assert.That( SmtpRepository.IsValidEmailAddress( string.Empty ), Is.False );
      }
   }

Das war’s !
Mit weiteren Tests kann das Verhalten unserer Klasse komplett abgedeckt und danach mit TDD die Klasse erweitert oder verändert werden.
Und das alles ohne die Befürchtung, dass diese Änderungen zu unvorhersehbaren Ergebnissen führen.