Posts Tagged ‘Rhino.Mocks’

AfterLaunch – Folien zum Vortrag TDD mit MSTest

Saturday, April 12th, 2008

Die gestrige AfterLaunch Veranstaltung war ein voller Erfolg! Ein Tag voller Vorträge und spannender Diskussionen. Ich denke den ein oder anderen wird man in Köln oder Bonn bei den regelmäßigen Treffen der User Groups wieder sehen.

Hier gibt’s meine Folien zum Vortrag TDD mit MSTest.

NDepend und LoD Vorträge bei Bonn-to-Code

Wednesday, February 27th, 2008

Meine Vorträge zu NDepend und dem Law of Demeter gestern Abend bei der .NET Community in Bonn (siehe http://www.bonn-to-code.net) waren mit 37 Zuhörern gut besucht.

  • PowerPoint Folien zu NDepend gibt’s hier.
  • PowerPoint Folien zum LoD und Mock Frameworks gibt’s hier.
  • VS2008 Solution zum LoD und Mocks gibt’s hier.

TypeMock – Erste Schritte

Sunday, February 17th, 2008

Aufgrund der Diskussionen während der ALT.NET UK Konferenz habe ich beschlossen mir TypeMock mal genauer anzuschauen. Vor allem der Support für Method Chaining interessiert mich. Aber immer der Reihe nach…

TypeMock ist ein kommerzielles Mock Framework. Es ist in drei Varianten verfügbar: Community, Professional und Enterprise (siehe Gegenüberstellung der Features). TypeMock verwendet das Profiler API um Aufrufe von Methoden abzufangen. Bisher arbeite ich mit Rhino.Mocks das für diesen Zweck Castle DynamicProxy verwendet. Mock Frameworks die auf Proxys basieren können lediglich Interfaces und virtuelle Methoden mocken. Das Ersetzen einer konkreten Klasse die kein Interface implementiert oder das Ersetzen nicht-virtueller oder statischer Methoden (inkl. Extension Methods) oder LINQ Querys ist mit solchen Frameworks technisch bedingt nicht möglich.

Bislang ging ich davon aus dass dies in der Praxis keine wirkliche Einschränkung für mich ist da es dazu führt, Klassen lose zu koppeln damit sie getestet werden können. Während der ALT.NET UK Konferenz wurde von Roy Osherove die Befürchtung geäußert dass man als Software Entwickler Designentscheidungen zur losen Kopplung von Komponenten weiter treibt als eigentlich sinnvoll, “nur” damit die Klassen getestet werden können. TypeMock verspricht diese technische Einschränkung aufzuheben.

In den Beispielen verwende ich ein weiteres Framework zum ersten mal: MSTest. Bislang habe ich NUnit eingesetzt (und werde das wohl auch weiterhin tun). Da MSTest in Visual Studio integriert ist und Code Coverage Analyse mitbringt bin ich sehr gespannt wie es sich für einen NUnit Nutzer “anfühlt”.

Vorbereitungen:

  • TypeMock Download von hier.
  • Installation
  • Tools | Add-in Manager… | TypeMock Isolator aktivieren

Erster Test:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using TypeMock;

namespace Model
{
  [TestClass]
  public class CustomerTests
  {
    [TestMethod]
    public void Orders_can_be_mocked() {
      Order order = RecorderManager.CreateMockedObject<Order>();
      Customer customer = new Customer();

      using(RecordExpectations mock = RecorderManager.StartRecording()) {
        mock.ExpectAndReturn(customer.Orders, new Order[] { order });
      }

      Assert.AreSame(order, customer.Orders[0]);
    }
  }
}

Wenn TypeMock nicht aktiviert sein sollte erhält man folgende Fehlermeldung. Vorbildlich dass in der Meldung gleich beschrieben ist wie man das Problem abstellt!

Test method Orders_can_be_mocked threw exception:  TypeMock.TypeMockException:
*** Typemock Isolator is not currently enabled.
To enable do one of the following:

* To run Typemock Isolator as part of an automated process you can:
   - run tests via TMockRunner.exe command line tool
   - use 'TypeMockStart' tasks for MSBuild or NAnt

* To work with Typemock Isolator inside Visual Studio.NET:
        set Tools->Enable Typemock Isolator from within Visual Studio

For more information consult the documentation (see 'Running' topic).

Kein Fluent Interface

Da ich die Syntax von Rhino.Mocks gewohnt bin vermisse ich das Fluent Interface mit seiner compiletime Typsicherheit (siehe dazu hier):

// TypeMock
mock.ExpectAndReturn(customer.Orders, new Order[] { order });

// Rhino.Mocks
Expect.Call(customer.Orders).Return(new Order[] { order });
 
Bei TypeMock ist der Parameter der Methode ExpectAndReturn vom Typ object so dass die Typprüfung erst zur Laufzeit erfolgen kann. Rhino.Mocks Fluent Interface verwendet Generic Methods so dass der Parameter der Return Methode aus dem Typ des Property inferiert wird. Das macht sich schon bei der Eingabe sehr positiv bemerkbar, da ReSharper einen Typkonflikt sofort anzeigt, die Prüfung erfolgt statt zur Laufzeit bereits zur Compilezeit. Mit TypeMock kann man folgenden Test schreiben (und kompilieren!):
 
[TestMethod]
public void WrongReturnType() {
  Customer customer = new Customer();

  using (RecordExpectations mock = RecorderManager.StartRecording()) {
    mock.ExpectAndReturn(customer.Orders, 42);
  }
}
 
Natürlich gibt es beim Ausführen des Tests eine Exception da der Rückgabewert von Customer.Orders vom Typ IList<Order> ist und nicht int.
 
Test method WrongReturnType threw exception:  TypeMock.TypeMockException:
*** Method get_Orders in type Model.Customer cannot return System.Int32.
 
Dies hat einen erheblich größeren Einfluss auf ein flüssiges Arbeiten als man zunächst vermuten würde: wenn man im Rahmen eines Refactorings den Typ eines Properties ändert lässt der Compiler die zugehörigen Tests durchgehen da sie syntaktisch korrekt sind. Das Feedback kommt also erst zur Laufzeit der Tests. Beim Einsatz von Rhino.Mocks liefert ReSharper (oder etwas später der Compiler) bereits eine Meldung dass die Typen nicht mehr passen. Ging es bei TDD nicht um kurze Feedback Loops?

Method Chaining

Nun zu einem Highlight von TypeMock: geschachtelte Methodenaufrufe können in einem Rutsch durch ein Mock Objekt ersetzt werden.
 
[TestMethod]
public void MethodChaining() {
  Customer customer = new Customer();

  using (RecordExpectations r = RecorderManager.StartRecording()) {
    r.ExpectAndReturn(customer.Orders[0].OrderItems[0].Product.Color, "Red");
  }

  Assert.AreSame("Red", customer.Orders[0].OrderItems[0].Product.Color);
}
 
Beim Einsatz von Rhino.Mocks hätte ich folgende Mock Objekte erstellen müssen: order, orderItem, product. Erst das letzte Objekt in der Kette hätte den gewünschten Wert “Red” geliefert:
 
[TestMethod]
public void MethodChaining_Rhino() {
  MockRepository mocks = new MockRepository();
  IOrder order = mocks.DynamicMock<IOrder>();
  IOrderItem orderItem = mocks.DynamicMock<IOrderItem>();
  IProduct product = mocks.DynamicMock<IProduct>();

  Customer customer = new Customer();
  customer.Orders.Add(order);
  using (mocks.Record()) {
    Expect.Call(order.OrderItems).Return(new IOrderItem[] {orderItem});
    Expect.Call(orderItem.Product).Return(product);
    Expect.Call(product.Color).Return("Red");
  }
  using (mocks.Playback()) {
    Assert.AreSame("Red", customer.Orders[0].OrderItems[0].Product.Color);
  }
}
 
Damit dies mit Rhino.Mocks (bzw. anderen auf Proxys basierenden Mock Frameworks) funktioniert müssen für die Klassen Order, OrderItems und Product Interfaces definiert sein oder die zu mockenden Properties Order.OrderItems, OrderItem.Product, Product.Color müssen virtual sein. Hier punktet TypeMock ganz eindeutig!! Ob ich das Design konkret so wählen würde wie im Beispiel oben sei dahin gestellt (siehe zum Beispiel Law of Demeter). Wichtig ist, mit TypeMock habe ich die Freiheit dies so zu entscheiden.

Fazit

TypeMock bietet neue Freiheiten beim Testen. Es läßt Softwaredesigns zu bei denen die Entkopplung nicht auf die Spitze getrieben ist, und die trotzdem testbar sind. Mit TypeMock ist eine größere Klasse von Softwaredesigns testbar als mit Rhino.Mocks. Für mich ist das eindeutig ein Vorteil, denn nun kann ich im Einzelfall entscheiden wie weit ich die Entkopplung treiben will. Fragwürdige Designs sollten durch Code Reviews und Pair Programming aufgedeckt werden, nicht durch technische Einschränkungen der verwendeten Tools.
 
Wenn TypeMock das Fluent Interface von Rhino.Mocks hätte würde ich sofort umsteigen. So zögere ich noch…
 

Law of Demeter (LoD)

Friday, February 15th, 2008

Ein wichtiges Prinzip beim Softwaredesign ist die lose Kopplung von Komponenten. Je stärker Komponenten gekoppelt sind desto schwieriger wird es diese Komponenten weiter zu entwickeln und zu pflegen. Das Law of Demeter dient dazu eine zu enge Kopplung zu erkennen. Es wurde 1989 von Prof. Ian Holland beschrieben. Die Regeln sind einfach.

Eine Methode sollte nur folgende andere Methoden verwenden:

  • Methoden der eigenen Klasse
  • Methoden der übergebenen Parameter
  • Methoden der mit eigenen Klasse assoziierten Klassen
  • Methoden von Objekten die die Methode selbst erzeugt hat

Desgleichen gilt natürlich für Properties und public Fields.

Lange Lindwürmer wie customer.Orders[0].OrderItem[0].Product.Name sind “verboten” (sie sind zumindest ein Hinweis auf eine enge Kopplung).

In der Praxis sollte man das Einhalten der Regeln mit Augenmaß befolgen und sich nicht sklavisch daran halten. In diesem Sinne ist der Begriff “Law” (Gesetz) eher irreführend. Im folgenden möchte ich anhand eines Beispiels zeigen wie man das Law of Demeter anwenden kann um zu einem lose gekoppelten Design zu kommen. Als Beispiel soll folgender Use Case dienen:

  • Ein Kunde soll über Statusänderungen seiner Aufträge per EMail informiert werden.

Eine erste Implementierung könnte wie folgt aussehen:


public class Order
{
  // ...
  public OrderStatus Status {
    get { return m_Status; }
    set {
      if (m_Status == value) {
        return;
      }
      SendOrderStatusChangedEMail(Customer.DefaultContact.EMail, m_Number, m_Status, value);
                                //*****************************\\
      m_Status = value;
    }
  }

  private static void SendOrderStatusChangedEMail(string email, string number,        OrderStatus oldStatus, OrderStatus newStatus) {
    m_MailService.Send(
      email,
      "Order status changed",
      string.Format("The status of your order {0} changed from {1} to {2}.",
        number, oldStatus, newStatus));
  }
}

Die kritische Stelle ist durch Sternchen markiert. Der Zugriff auf Customer.DefaultContact.EMail verstößt gegen das LoD. Während der Zugriff auf das Property Customer noch in Ordnung ist, sind die nachfolgenden Zugriffe innerhalb des Customer Objekts nicht mehr durch die LoD Regeln gedeckt. Doch wo liegt das Problem?

Die Klasse Order verwendet auf diese Weise Details der Klasse Customer. So “weiß” die Klasse Order dass Customer einen DefaultContact hat und in diesem wiederum eine EMailadresse hinterlegt ist. Möchte man die Details der Customer oder Contact Klassen später ändern, muss die Klasse Order angepasst werden.

Ein weiteres Problem ergibt sich beim Testen der Klasse Order. Um zu prüfen ob eine korrekte EMail abgesendet wird muss das zu testende Order Objekt ein Customer Objekt enthalten, dieses muss wieder einen DefaultContact enthalten bei dem die EMailadresse gesetzt ist. Der Aufbau der Testobjekte wird so recht aufwendig:

[SetUp]
public void Setup() {
  mocks = new MockRepository();
  customer = mocks.DynamicMock<ICustomer>();
  contact = mocks.DynamicMock<IContact>();
  mailService = mocks.DynamicMock<IMailService>();
  Order.MailService = mailService;

  order = new Order(customer, "1234");
}

[Test]
public void Order_Status_change_is_mailed_to_Customer() {
  using (mocks.Record()) {
    Expect.Call(customer.DefaultContact).Return(contact);
    Expect.Call(contact.EMail).Return("stefan@lieser-online.de");
    Expect.Call(delegate {
      mailService.Send(
        "stefan@lieser-online.de",
        "Order status changed",
        "The status of your order 1234 changed from Open to Verified.");
      });
  }
  using (mocks.Playback()) {
      order.Status = Order.OrderStatus.Verified;
  }
}

Eine alternative Implementierung vermeidet die enge Kopplung und vereinfacht damit auch die Tests:

public OrderStatus Status {
  get { return m_Status; }
  set {
    if (m_Status == value) {
      return;
    }
    Customer.SendEMail("Order status changed",
        string.Format("The status of your order {0} changed from {1} to {2}.",    Number, m_Status, value));
    m_Status = value;
  }
}

Statt sich durch das Customer Objekt durchzuhangeln gibt das Order Objekt dem Customer Objekt die Anweisung eine EMail zu versenden. Wie das Customer Objekt dies bewerkstelligt interessiert die Order Klasse nicht. Statt das Customer Objekt nach Details zu befragen wird ihm ein Auftrag erteilt (Tell don’t ask). Der Test sieht wie folgt aus:

[SetUp]
public void Setup() {
  mocks = new MockRepository();
  customer = mocks.DynamicMock<ICustomer>();
  order = new Order(customer, "56");
}

[Test]
public void Customer_gets_informed_about_order_status_change() {
  using (mocks.Record()) {
    Expect.Call(delegate {
      customer.SendEMail("Order status changed",
        "The status of your order 56 changed from Open to Verified.");
    });
  }
  using (mocks.Playback()) {
    order.Status = Order.OrderStatus.Verified;
  }
}

So führt die Berücksichtigung des Law of Demeter dazu dass das Single Responsibility Principle (SRP) befolgt wird. Die Order Klasse beschäftigt sich nun nicht mehr mit den Details des EMailversand.

Aber auch die strikte Einhaltung des Law of Demeter bringt Probleme mit sich: um nicht auf Methoden zuzugreifen die tief verschachtelt in anderen Objekten implementiert sind müsste man diese Methoden durch Wrapper in der Hierarchie nach oben ziehen:

// Verletzung des Law of Demeter
Color color = customer.Orders[i].OrderItems[j].Product.Color;

// Verwendung von Wrapper Methoden
Color color = customer.GetColorOf(i, j);

// Dazu müssen folgende Wrapper implementiert werden:
class Customer {
  Color GetColorOf(int orderIndex, int orderItemIndex) {
    return Orders[orderIndex].GetColorOf(orderItemIndex);
  }
}

class Order {
  Color GetColorOf(int orderItemIndex) {
    return OrderItems[orderItemIndex].ProductColor;
  }
}

class OrderItem {
  Color ProductColor {
    get { return Product.Color; }
  }
}

Vorteil dieser Vorgehensweise: beim Testen mit Mock Objekten reicht es aus für das Customer Objekt einen Mock zu erstellen und das Ergebnis der GetColorOf Methode zu definieren. Bei der Verkettung der Aufrufe müsste für jede Ebene der Verkettung ein Mock erzeugt werden. Dies gilt allerdings nur für Mock Frameworks die nicht mit dem Profiler API arbeiten, TypeMock kann solche “chained calls” unmittelbar behandeln.

Nachteil der Vorgehensweise: in den Klassen werden zahlreiche Wrapper implementiert um weiter unten liegende Eigenschaften nach oben zu transportieren. Die Klassen werden dadurch eher unübersichtlich. Abhilfe können Adapter schaffen, doch dazu schreibe ich ein anderes mal…

Mein persönliches Fazit: das Law of Demeter gibt mir Hilfestellung beim Erkennen von Kopplung. Die Designentscheidung ist damit jedoch nicht automatisch getroffen.

Mehr zum Thema gibt es übrigens am 26. Februar beim nächsten Bonn-to-Code Treffen siehe http://www.bonn-to-code.net/1540.aspx.

Technorati-Tags: ,,