Design Patterns: Teil 3 - Testbarkeit

Ziel dieser Blogreihe soll es nicht sein Pattern A bis Z vorzustellen (dafür gibt es genügend Bücher) sondern praktische Tutorials zu liefern, wie man Design Patterns und Entwicklungsmethodiken einsetzen kann um sich das Entwicklerleben zu erleichtern und die Qualität des Codes zu verbessern.

In den vorangegangen Artikeln der Design Patterns Reihe haben wir schrittweise eine modulare und lose gekoppelte Architektur entwickelt. Am Beispiel des LogWriters war schön zu erkennen, wie einfach und schnell man Modularisierung und lose Kopplung auch in scheinbar kleinen Anwendungen (die ja meist dann zu unternehmenskritischer Software heranwachsen) erreichen kann. Jetzt gehen wir einen Schritt weiter und überlegen wozu man das ganze verwenden kann. Eine gute Architektur sollte im Entwicklungsprozess auch so gut als möglich “ausgenutzt” werden, denn die Architektur allein schafft noch keinen hochqualitativen Entwicklungsprozess. Der erste Schritt dazu sind Entwicklertests! Damit ist nicht Debugging gemeint sondern eine Testgetriebene-Entwicklung, die die Brücke zwischen funktionierendem Code und Code, der funktioniert UND der Spezifikation entspricht, schlägt. Test-Driven-Development (TDD), das MVC Framework für ASP.NET und die hier vorgestellten Tools werden noch genauer in Folgeartikeln behandelt, vorerst wollen wir anhand eines kurzen Beispiels mittels ASP.NET MVC ansehen, wie leicht man Unit-Tests in einer gut strukturierten Anwendung unterbringen kann. Bis dahin sei auf die Links unten verwiesen.

Erstellt man in Visual Studio ein neues MVC Projekt aus der Vorlage “ASP.NET MVC 2 Web Application”, welche via Download des ASP.NET MVC Frameworks mitgeliefert wird, wird man anfangs sofort gefragt, ob man seiner neuen Solution gleich Unit Tests hinzufügen möchte – wollen wir natürlich!

A1

Im Anhang ist der Source Code einer Minianwendung zu finden, die nichts anderes tut als eine Eingabemaske zur Verfügung zu stellen um einen Kunden neu anzulegen.

Was wollen wir erreichen? Wir wollen nun testen ob beim Anlegen eines Kundendatensatzes die Muss-Felder geprüft werden und entsprechende Fehlermeldungen erscheinen. Was wir nicht wollen, ist das bei jedem Testdurchlauf Daten in die Datenbank geschrieben werden – 1. dauert das lange wenn viele Tests laufen und 2. müsste bei komplexeren Szenarien jedesmal die Datenbank zurückgesetzt werden. Genau hier kommt unsere Architektur wie wir sie im letzten Artikel erarbeitet haben zum Einsatz.

Dazu implementieren wir den CustomerController folgendermassen:

    1: private readonly ICustomerRepository _customerRepository;
    2:
    3: public CustomerController(ICustomerRepository customerRepository)
    4: {
    5:     _customerRepository = customerRepository;
    6: }
    7:
    8: public ActionResult Create(Customer customer)
    9: {
   10:     if(string.IsNullOrEmpty(customer.FirstName))
   11:     {
   12:         ModelState.AddModelError("FirstName","Firstname must not be empty!");
   13:     }
   14:     _customerRepository.Save(customer);
   15:     return View();
   16: }

Wichtig ist hierbei nur die Tatsache, dass die Abhängigkeit des Controllers vom CustomeRepository, dass das eigentlich Speichern übernimmt injeziiert wird.

Das ermöglicht uns dass wir ein “gefaktes” CustomerRepository implementieren, also eine Klasse “CustomerRepositoryMock” die einfach ICustomerRepository implementiert und die Methode Save() so defniert wie wir sie zu Testzwecken wollen. Nämlich entweder komplett ohne Funktion oder es könnte auch eine In-Memory Datenbank wie SQLite verwendet werden. Die Implementierung ist im anghängten Sourcecode ersichtlich um den Artikel kurz zu halten. Im Prinzip folgen ICustomerRepository, CustomerRepository und CustomerRepositoryMock dem gleichen Prinzip wie die LogWriter im vorigen Artikel.

Das Schöne ist nun, dass die Methode Create() des CustomerControllers einfach getestet werden kann und GANZ WICHTIG (!!) sie muss NICHT geändert werden um produktiv zu funktionieren! Es muss nur die injeziierte Instanz von ICustomerRepository zB via Windsor Container umgestellt werden

-> siehe Design Patterns Teil 2

Der Test sieht dann so aus:

    1: [TestMethod]
    2: public void ValidationMessagesShouldAppearOnCreate()
    3: {
    4:     var custController = new CustomerController(
    5:     new CustomerRepositoryMock());
    6:
    7:     var customer = new Customer()
    8:                                {FirstName = ""};
    9:
   10:     custController.Create(customer);
   11:
   12:     Assert.AreEqual(1, custController.ModelState.Count);
   13:     Assert.AreEqual("Firstname must not be empty!", custController.ModelState["FirstName"].Errors[0].ErrorMessage);
   14:
   15: }

Die 2 Assert() Anweisungen prüfen, ob im ModelState der Key “FirstName” vorhanden ist und ob der Eintrag die richtige Fehlermeldung enthält.

A11111

Hier ist auch schön zu sehen, wie einfach die UI im ASP.NET MVC Framework getestet werden kann.

Eine Einführung ins MVC Framework und noch viel mehr zu TDD und Unit Testing folgen in den nächsten Artikeln!

Links/Downloads:

Beispiel Source Code ASP.NET MVC Framework Einführung in TDD