Archiv

Archive for August 2012

Moq für komplexere Anwendungsfälle

29. August 2012 Kommentare aus

Moq ist eine kleine Mock-Bibliothek, die ich hier bereits einmal vorgestellt habe. Für die meisten Anwendungsfälle genügt es die Rückgabewerte von Methoden zu beeinflussen. Ab und zu wäre es aber praktisch ein wenig mehr mit den Mocks machen zu können. Heute zeige ich wie Moq einem bei spezielleren Anwendungsfällen unterstützen kann.

 

Flexible Rückgabewerte

Mit Moq ist es kein Problem verschiedene Rückgabewerte zu liefern – so lange sich die Parameter beim Methodenaufruf unterscheiden. Der notwendige Code um den Wert „A“ für die Id 1 und „B“ für die Id 2 zu erhalten sieht so aus:

public interface ISimpleDemo
{
    string GetValue(int id);
}

[TestMethod]
public void MultipleReturnsForDifferentInputs()
{
    var mock = new Mock<ISimpleDemo>();
    mock.Setup(service => service.GetValue(1)).Returns("A");
    mock.Setup(service => service.GetValue(2)).Returns("B");

    Assert.AreEqual("A", mock.Object.GetValue(1));
    Assert.AreEqual("B", mock.Object.GetValue(2));
}

Hin und wieder steht man aber vor der Aufgabe bei einer Methode ohne Parameter verschiedene Rückgabewerte definieren zu müssen. Der direkte Versuch die gewünschten Werte nach einander zu definieren schlägt leider fehl:

public interface IBlogDemo
{
    string GetNextName();
}

[TestMethod]
public void MultipleReturnsTheWrongWay()
{
    var mock = new Mock<IBlogDemo>();
    mock.Setup(service => service.GetNextName()).Returns("A");
    mock.Setup(service => service.GetNextName()).Returns("B");
    mock.Setup(service => service.GetNextName()).Returns("C");

    Assert.AreEqual("A", mock.Object.GetNextName()); // Gibt "C"
    Assert.AreEqual("B", mock.Object.GetNextName());
    Assert.AreEqual("C", mock.Object.GetNextName());
}

So überschreibt man nur den Rückgabewert und der Tests endet mit dieser Fehlermeldung:

Assert.AreEqual failed. Expected:<A>. Actual: <C>.

Damit man diesen Test erfüllen kann benötigt es einen anderen Ansatz. Mit Hilfe einer Queue kann man die Werte in der gewünschten Reihenfolge ablegen (auch null-Werte sind so möglich). Die Returns-Funktion von Moq ruft nun die Dequeue-Methode auf und bekommt die für den Test benötigten Werte zurück:

[TestMethod]
public void MultipleReturnsRight()
{
    var mock = new Mock<IBlogDemo>();
    var results = new List<string> {"A", "B", "C", null};
    var pq = new Queue<string>(results);

    mock.Setup(c => c.GetNextName()).Returns(pq.Dequeue);

    Assert.AreEqual("A", mock.Object.GetNextName());
    Assert.AreEqual("B", mock.Object.GetNextName());
    Assert.AreEqual("C", mock.Object.GetNextName());
    Assert.IsNull(mock.Object.GetNextName());
    // ==> Funktioniert
}

 

Wurde eine Methode aufgerufen?

Moq bietet einem die Möglichkeit einzelne Aspekte des verwendeten Mocks zu überprüfen. Muss man wissen wie oft eine Methode des Mocks aufgerufen wurde ist die Funktion Verify sehr hilfreich. Der notwendige Code um zu prüfen ob GetValue mit Parameter 1 genau einmal aufgerufen wurde wird so sehr einfach:

[TestMethod]
public void ExactlyOnceCalled()
{
    var mock = new Mock<ISimpleDemo>();

    mock.Object.GetValue(1);

    mock.Verify(db => db.GetValue(1), Times.Once());
}

Wird die Methode nicht mit dem erwarteten Parameter aufgerufen (zum Beispiel in dem man im oberen Code-Ausschnitt GetValue mit dem Wert 2 aufruft), wirft Moq eine entsprechende Fehlermeldung:

Test method TestProject1.UnitTest1.ExactlyOnceCalled threw exception:
Moq.MockException:
Expected invocation on the mock once, but was 0 times: db => db.GetValue(1)
No setups configured.
Performed invocations:
ISimpleDemo.GetValue(2)

Es kann vorkommen das man nicht genau weiss mit welchem Parameter die Methode aufgerufen wurde (wenn beispielsweise in der zu testenden Klasse neue Instanzen erzeugt werden). In diesem Fall kann man die It-Klasse verwenden:

[TestMethod]
public void ExactlyOnceCalledUnknownParameter()
{
    var mock = new Mock<ISimpleDemo>();

    mock.Object.GetValue(1);

    mock.Verify(db => db.GetValue(It.IsAny<int>()), Times.Once());
}

Auch bei der Anzahl der Aufrufe ist Moq sehr flexibel. Ob eine Methode einmal, nie, genau x mal oder innerhalb eines Bereiches aufgerufen wurde kann man alles mit der Times-Klasse überprüfen lassen:

[TestMethod]
public void TimesHasManyUsages()
{
    var mock = new Mock<ISimpleDemo>();

    mock.Object.GetValue(1);
    mock.Object.GetValue(2);
    mock.Object.GetValue(2);

    mock.Verify(db => db.GetValue(1), Times.Once());
    mock.Verify(db => db.GetValue(2), Times.AtLeastOnce());
    mock.Verify(db => db.GetValue(2), Times.Between(1, 2, Range.Inclusive));
    mock.Verify(db => db.GetValue(2), Times.Between(1, 3, Range.Exclusive));
    mock.Verify(db => db.GetValue(2), Times.Exactly(2));
    mock.Verify(db => db.GetValue(3), Times.Never());
}

 

Fazit

Moq unterstützt einem auch bei komplexeren Einsatzszenarien. Die Möglichkeiten von Verify sind vielfältig und können einem dabei helfen so manches Testszenario abzudecken. Dabei sind It.IsAny und Times eine grosse Hilfe.

Allerdings darf man bei so viel Flexibilität das Ziel des Tests nicht aus den Augen verlieren. Sobald man sich auf zu viele Implementationsdetails einlässt werden die Tests sehr schnell unwartbar. Im Zweifel sollte man daher lieber erst versuchen mit einer besseren Code-Struktur den Test einfacher zu gestalten.

Schlagworte: ,

Windows virtualisieren leicht gemacht

14. August 2012 1 Kommentar

Das Release von Visual Studio 2012 steht unmittelbar bevor. Und Windows 8 kommt auch schon bald. Bei all den Updates fragt man sich schnell einmal: Funktioniert nach dem Update alles so gut wie jetzt? Laufen meine Templates noch? Sind alle nötigen Erweiterungen bereits portiert? Oder muss ich mich erst noch mit Kinderkrankheiten herumschlagen?

Wie toll wäre es wenn man gefahrlos all die neuen Versionen testen könnte. Geht nicht? Zu aufwändig? Nicht wenn man seinen Entwickler-PC virtualisiert.

 

Disk2vhd macht es möglich

Das kleine Werkzeug Disk2vhd von Sysinternals hat sich für meinen Bedarf als äusserst nützlich erwiesen. Nach dem herunterladen genügt es die *.exe Datei zu starten und schon steht man vor einem sehr einfachen Dialog:

Bei „VHD File name“ gibt man die Datei an, in die das Abbild der Harddisk geschrieben werden soll. Die ausgewählten Partitionen werden durch einen Klick auf „Create“ kopiert. Die Daten werden beim Schreiben auch gleich komprimiert, wodurch man deutlich weniger Speicherplatz benötigt als die Originalfestplatten füllen.

 

VHD Datei einbinden

VHD als Dateiformat wird mittlerweile von recht vielen Virtualisierungslösungen unterstützt. Ich selber nutze VirtualBox, wo ein Einbinden ebenfalls ohne Konvertierung möglich ist.

Um das gerade erzeugte Image zu nutzen erzeugt man eine neue virtuelle Maschine. Im Dialog zur Virtuellen Festplatte kann man die VHD-Datei auswählen. Hat man alle weiteren Angaben ausgefüllt speichert man die Einstellungen und startet die virtuelle Maschine.

Wenn alles ohne Fehlermeldung funktionierte hat man nun seine gewohnte Umgebung in einer virtuellen Maschine. Einem testen des neuen Visual Studio oder eines anderen Produktes steht so nichts mehr im Weg.

 

Fazit

Mit Hilfe von Disk2vhd kann man in sehr kurzer Zeit ein Abbild seines Computers erzeugen und in einer virtuellen Maschine laufen lassen. Neue Versionen und Bugfixes lassen sich so testen ohne dass man seine Umgebung einem Risiko aussetzt.

Mir gefällt an diesem Ansatz vor allem der geringe Aufwand. Wenn es so einfach geht macht man dies auch. Und als Nebeneffekt kann man die VHD-Datei auch für einen Restore nutzen, sollte man seinen PC doch einmal neu installieren müssen.

Schlagworte: ,

Buch-Rezension zu “Seven Databases in Seven Weeks”

8. August 2012 Kommentare aus

Seven Databases in Seven Weeks” von Eric Redmond und Jim R. Wilson erschien im Mai 2012 bei The Pragmatic Programmers. Die beiden Autoren liefern mit ihrem Buch eine gute Übersicht zum Thema NoSQL.

Dieses Buch lehnt sich nicht nur beim Titel bei “Sieben Wochen, Sieben Sprachen” an. Beide Bücher erklären in jeweils 3 Tageslektionen die vorgestellten Themen. Bei den Datenbanken wird aber bereits am ersten Tag tief in die Spezialitäten eingestiegen. Um die vorgestellten Übungen selber nachzuvollziehen sollte man wiederum ein UNIX-basiertes Betriebssystem nutzen.

 

Die 7 Datenbanken

PostgreSQL
NoSQL steht für Not only SQL, nicht für No SQL. Daher ist es nicht erstaunlich dass als erste Datenbank mit PostgreSQL ein Vertreter relationaler Datenbanken behandelt wird. Mit PostgreSQL werden Tabellen, Views und die Abfragesprache SQL erklärt. So verfügt jeder über das nötige Grundwissen um die Unterschiede zu den anderen Datenbanken verstehen zu können.

Wer sich bereits mit PostgreSQL beschäftigt hat kann hier aber auch einiges lernen: Window Functions, Hypercubes oder Pivot Tabellen über die Funktion crosstab() sind wohl nichts was man täglich benötigt. Bei einer entsprechenden Problemstellung ist es aber gut zu wissen dass dies auch mit PostgreSQL machbar ist.

 

Riak
Alternativen zur Abfragesprache SQL werden mit der verteilten Key-Value Datenbank Riak eingeführt. Im Buch wird fürs speichern und auslesen der Daten vor allem die HTTP REST Schnittstelle verwendet. So braucht man nicht extra eine zusätzliche Programmiersprache zu lernen. Ob cURL bei komplexen Abfragen und Funktionsdefinitionen die richtige Wahl ist sei aber dahingestellt.

Mit Riak kann man problemlos skalieren und bei Bedarf zusätzliche Server hinzufügen. Muss man seine Daten aber flexibel abfragen können stösst man sehr bald an seine Grenzen.

 

HBase
HBase ist eine spaltenorientierte Datenbank. Dies bedeutet das die Inhalte spaltenweise statt wie bei relationalen Datenbanken zeilenweise gespeichert werden. Speichert man beispielsweise Datensätze mit den Feldern Id und Name, so werden erst alle Ids abgelegt und danach alle Namen. Will man wie bei Data-Warehouse Anwendungen üblich grosse Datenmengen über spezifische Felder verdichten kann man die gewünschten Daten schön aneinandergereiht von der Festplatte lesen. Man liest nur was man braucht und nicht immer noch alle anderen Informationen die den Datensatz bilden.

HBase ist sehr gut geeignet um Terabytes an Daten zu speichern. Alle Daten werden automatisch versioniert und komprimiert. Gemäss den Autoren sollte man eine HBase Installation immer mit mindestens 5 Servern machen. Damit ist HBase für kleine Anwendungen nicht wirklich geeignet.

 

MongoDB
Mit MongoDB wird die erste dokumentenorientierte Datenbank vorgestellt. Als Dokument ist hier keine Word-Datei gemeint sondern eher etwas wie eine XML-Datei die einen beliebigen Inhalt haben kann. Der Name MongoDB kommt vom englischen Humongous und bedeutet so viel wie gigantisch.

Durch die Möglichkeit schemalose Daten zu speichern ist MongoDB äusserst flexibel. Die Performance von MongoDB ist sehr gut, bis die aktiven Daten mehr Platz benötigen als im RAM gespeichert werden kann. Ab dem Moment muss zwingend in die Breite skaliert werden, was nicht ganz so einfach ist wie bei Riak.

 

CoucheDB
CoucheDB ist wie MongoDB eine dokumentenorientierte Datenbank. Auch hier begegnen einem JavaScript und JSON, doch damit enden die Gemeinsamkeiten. Die Implementierung, die Projektphilosophie und die Skalierungsansätze unterscheiden sich grundlegend.

CoucheDB kann vom Smartphone bis zum Enterprise Server überall laufen. Allerdings ist CoucheDB nicht wirklich gemacht für ad hoc Abfragen. MongoDB und CoucheDB zeigen sehr schön wie verschieden man die gleiche Grundidee implementieren kann. Und wie wichtig es ist genau zu schauen ob die gewählte Datenbank auch wirklich für den geplanten Einsatz geeignet ist.

 

Neo4J
Neo4J ist eine Graph-Datenbank und ist besonders geeignet um Beziehungen zwischen einzelnen Elementen abzulegen. Was bei relationalen Datenbanken sehr schnell an die Performance geht ist mit Neo4J sehr einfach abbildbar. Dadurch dass man beliebige Daten verknüpfen kann ist man noch flexibler als bei den dokumentenorientierten Datenbanken.

Im Moment kann Neo4J nur den ganzen Graphen replizieren. Eine Skalierbarkeit wie beim Sharding in MongoDB ist dadurch nicht möglich. Will man Neo4J kommerziell nutzen sollte man die Lizenzgebühren im Auge behalten.

 

Redis
Zum Abschluss wird mit Redis noch einmal ein Key-Value Datenbank behandelt. Im Gegensatz zu Riak kann man bei Redis deutlich komplexere Strukturen (wie Listen, Mengen, Hashes) als Key nutzen. Da Redis oft als eine In-Memory Datenbank verwendet wird kann man sehr schnell auf die Daten zugreifen.

Wenn die Daten nur im RAM liegen verliert man diese bei einem Absturz. Um dem entgegen zu wirken gibt es mehrere Möglichkeiten um Redis zum Speichern der Daten zu bewegen. Auch bei Redis muss man einen Kompromiss zwischen Geschwindigkeit und Datensicherheit finden.

 

CAP Theorem

Mit verteilten Systemen möchte man diese 3 Ziele erreichen:

  • Consistency – alle Teile haben den gleichen Datenstand
  • Availability – alle Anfragen ans System werden immer beantwortet
  • Partition Tolerance – das System arbeitet auch wenn Teile davon nicht erreichbar sind

Das CAP-Theorem von Eric Brewer besagt das man von diesen 3 Anforderungen immer nur 2 umsetzen kann. Auf welche Anforderung man verzichten kann hängt dabei von der Anwendung ab.

Im Buch dient CAP auch zum klassieren der vorgestellten Datenbanken:

  • Redis, PostgreSQL und Neo4J erfüllen C & A, verteilen aber keine Daten.
  • MongoDB und HBase erfüllen C & P, Anfragen können fehlschlagen.
  • CouchDB erfüllt A & P, garantiert aber keine Konsistenz über alle Knoten.
  • Riak ermöglicht dem Benutzer pro Request selber zu wählen welche 2 Anforderungen erfüllt sein sollen.

 

MapReduce

MapReduce ist ein Algorithmus mit dem man grosse Datenmengen in einem verteilten System verarbeiten kann. Mit einer Map-Funktion werden die Ausgangsdaten in ein Format gepackt, das fürs Lösen der Aufgabe besser geeignet ist. (Wie das Aufteilen eines grossen Textes in einzelne Worte). Diese Daten werden an eine Reduce-Funktion übergeben die diese Daten verdichtet.

Mit einer grosszügigen Auslegung kann man dies mit der Group By Funktion relationaler Datenbanken vergleichen. Möchte man dort wissen welche Kunden die meisten Bestellungen gemacht haben könnte man so eine Abfrage machen:

SELECT customerId, count(*) FROM orders GROUP BY customerId;

Von ganzen Informationen die in der Orders-Tabelle sind interessiert uns nur das Feld customerId. Möchte man die Werte selber zählen (also eine eigene Reduce-Funktion machen) könnte man die Daten mit dieser „Map-Funktion“ erhalten:

SELECT customerId, 1 FROM orders;

Bei den NoSQL-Datenbanken wo jeder Datensatz anders aussehen könnte, ist der Hersteller nicht in der Lage Funktionen vorherzuahnen, mit denen man seinen Datenbestand analysieren kann. Stattdessen unterstützen die meisten im Buch vorgestellten Datenbanken MapReduce. So kann man nach diesem Muster selber seine Funktionen schreiben um die gewünschten Resultate zu erhalten.

 

Welche Datenbank soll man nehmen?

Bei all den unterschieden im Funktionsumfang gibt es auf diese Frage keine allgemeine Antwort. Erst wenn man die Anforderungen an die Datenbank kennt ist man in der Lage eine fundierte Entscheidung zu treffen.

Man sollte sich nicht zu sehr darauf fixieren eine einzige Datenbank zu finden die alle Anforderungen erfüllt. Die optimale Lösung für ein Projekt könnte im Zusammenspiel verschiedener Datenbanksysteme liegen. Dieser Ansatz ist unter dem Begriff “Polyglot Persistence” bekannt und erfreut sich einer immer grösseren Beliebtheit.

Im Buch wird eine Anwendung erstellt die auf Redis, CoucheDB und Neo4J setzt. So bekommt man einen sehr schnellen Cache, einen äusserst flexibler Datenspeicher und man kann Beziehungen zwischen den Datensätzen sehr schnell abfragen. So kann man alle Vorteile der einzelnen Systeme nutzen ohne von ihren Einschränkungen gebremst zu werden.

 

Fazit

Dieses Buch ist sehr gut geeignet um sich einen Überblick über verschiedene Konzepte zum Speichern von Daten zu verschaffen. Man wird sich beim Lesen sehr schnell bewusst das jede Datenbank sowohl Vor- wie auch Nachteile mit sich bringt. Je besser man die Anforderungen der eigenen Anwendung an die Datenbank kennt desto einfacher wird es eine fundierte Entscheidung zu fällen.

Man darf aber nicht erwarten nach dem Lesen ein Experte in Sachen NoSQL zu sein. Dafür sind die 350 Seiten zu kurz um 7 verschiedene Datenbanken zu erlernen. Das Buch liefert einem aber einen guten Startpunkt von dem aus man sein Wissen gezielt vertiefen kann.

 

Zum Buch

Seven Databases in Seven Weeks: A Guide to Modern Databases and the NoSQL Movement” von Eric Redmond und Jim R. Wilson, 2012 The Pragmatic Programmers, ISBN 978-1-9343-5692-0, 350 Seiten, Englisch

Schlagworte: ,
Follow

Erhalte jeden neuen Beitrag in deinen Posteingang.

Schließe dich 254 Followern an