Adventtipp 16 – Unit-Tests gegen Sitecore mit dem Glass-Mapper und den Sitecore Abstractions

 

Beim Unit-Test sollte das FIRST-Prinzip beachtet werden, um nützliche und effektive Tests zu schreiben.

Jeder Sitecore-Entwickler kennt die Sitecore-API, die im Wesentlichen aus statischen Klassen und Methoden besteht. So kann zum Beispiel mit

var currentItem = Sitecore.Context.Item;
var mainEntries = currentItem.Children.Where(ShowInNavigation).ToList();

sehr einfach und quasi von überall auf das Item für den aktuellen Request zugegriffen und damit weitergearbeitet werden.

Das ist natürlich sehr praktisch, wird aber spätestens dann zum Problem, wenn man versucht Unit-Tests für eine Methode zu schreiben, die eben solche Aufrufe nutzt.

FIRST-Prinzip

Um Unit-Tests gegen Sitecore zu schreiben existiert das Projekt FakeDb. Dieses kann grundsätzlich genutzt werden, allerdings ist dieser Ansatz relativ langsam in der Ausführung, da in Memory erst eine Datenbank aufgebaut werden muss. „Relativ“ deswegen, da sich auf diese Weise sicherlich bis zu 100 Tests performant genug umsetzen lassen. Bei größeren Projekten Bedarf es für eine gute Testabdeckung allerdings eher tausende Tests und dann sind einige Millisekunden Ausführungszeit pro Test in Summe auf einmal doch viel Zeit.

Das FIRST-Prinzip beschreibt, wie Tests aufgebaut sein sollten:

  • Fast: Die Ausführungsdauer eines Testfalls sollte im Millisekunden-Bereich liegen, damit auch mehrere tausend Unit-Tests innerhalb kürzester Zeit ausgeführt werden können. Dadurch soll gewährleistet werden, dass der Entwickler jederzeit schnell überprüfen kann, ob seine Änderungen bestehende Funktionalität beeinflusst.
  • Isolated: Jeder Testfall sollte nur eine bestimmte Sache prüfen. Wenn ein Test fehlschlägt kann bei Beachtung dieser Regel die Ursache des Problems schnell und zuverlässig aus der Benennung des Testfalls abgelesen werden.
  • Repeatable: Ein Test muss bei jeder Ausführung und unabhängig von der Reihenfolge der Ausführung dieselben Ergebnisse liefern. Sonst läuft man Gefahr ständig mit der Prüfung beschäftigt zu sein, ob es sich um einen „echten“ Fehler handelt.
  • Self-verifying: Jeder Testfall sollte mindestens eine Prüfung (Assertion) enthalten und sich nicht darauf verlassen, dass z.B. ein Entwickler bemerkt, wenn eine Exception in die Konsole geschrieben wird. Ein Test der nichts prüft ist ein nutzloser Test und kann genauso gut gelöscht werden.
  • Timely: Sollte man erst den Test und dann die Implementierung schreiben (TDD) oder erst die Implementierung und danach den Test (TAD)? Dazu sei gesagt, dass man TDD erst lernen und üben muss, bevor sich die Vorteile zeigen. Aber jeder der regelmäßig am Verweifeln ist, wenn er versucht seinen Code nachträglich „testbar“ zu bekommen sollte TDD definitiv ausprobieren.

Das Problem der Item-Klasse und der statischen Sitecore-API

Folgender Beispiel-Code angenommen:

staticcodesamplev2

Und der Versuch einen entsprechenden Unit-Test mit Hilfe von Moq zu schreiben:

staticcodesampletest_withglassandabstractions

Mit ein wenig Aufwand lässt sich der statische Aufruf Sitecore.Context.Item durch einen Mock ersetzen, da es sich um eine setzbare Property handelt:

Sitecore.Context.Item = itemMock.Object;

Problematisch wird es dann allerdings bei den folgenden beiden Versuchen:

1. Problem: Der Versuch currentItem.Children zu mocken

itemMock.SetupGet(item => item.Children).Returns(
                   new ChildList(itemMock.Object));

 

Hierbei wird man daran scheitern, dass es zu der Item-Klasse kein passendes Interface gibt, das man mocken könnte. Die Klasse Item kann man durchaus mocken, allerdings sind Methoden wie item.Parent oder item.Children nicht als virtual / überschreibbar markiert. Dies führt dann bei der Test-Ausführung zu folgender Fehlermeldung, da Moq nur virtuelle Methoden mocken kann:

System.NotSupportedException: Invalid setup on a non-virtual 
(overridable in VB) member: item => item.Children

 

2. Problem: Der Versuch LinkManager.GetItemUrl (item) zu mocken

Url = LinkManager.GetItemUrl(item)

 

An dieser Stelle hat man keinerlei Möglichkeiten zu beeinflussen, was die Methode GetItemUrl zurückliefert, da man den statischen LinkManager Aufruf nicht durch einen Mock ersetzen kann.

Die Lösung des 1. Problems: Glass-Mapper

Mit Glass.Mapper for Sitecore existiert ein Mapping-Framework, um Sitecore Items auf typisierte Objekte abzubilden.

Dadurch ändert sich die Implementierung des Beispiels wie folgt:

staticcodesample_withglass

 

Mehr Details zum Glass-Mapper erfahrt ihr in unserem Adventtipp vom 17.12.2016.

Die Lösung des 2. Problems: Sitecore-Abstractions

Mit Sitecore 7.5 wurde zum ersten Mal die Sitecore.Abstractions.dll ausgeliefert (http://www.partech.nl/nl/blog/2014/09/sitecore-abstractions-dll-in-sitecore-7-5). Die Sitecore Abstractions sind nichts anderes als Service-Wrapper um die statische Sitecore-API. Die jeweiligen Interfaces (z.B. ISettings für Sitecore.Configuration.Settings) bieten dieselben Methoden an, wie deren statische Varianten. Die entsprechenden Implementierungen rufen dann intern einfach die entsprechenden statischen Methoden auf.

Und was bringt das Ganze? Viel!!!

 

Dank der Interfaces können in Unit-Tests die echten Aufrufe durch eigene Aufrufe ersetzt werden! Und das ohne kostspielige kommerzielle Mocking-Frameworks. Der Einsatz eines einfachen Mocking-Frameworks wie z.B. Moq genügt, um die echten Implementierungen durch eigene Mocks oder Stubs zu ersetzen. Am besten setzt man die Sitecore Abstractions in Kombination mit einem Dependecy Injection Framework wie beispielsweise Unity ein.

Bis Sitecore 8.1 wurden mit Sitecore nur sehr wenige Abstractions mitgeliefert:

Mittelfristig soll die statische Sitecore API vollständig abgelöst werden, es lohnt sich also bereits jetzt mit der Umstellung zu beginnen, zumal diese meist recht einfach durchzuführen ist.Beim Implementieren merkt man schnell, dass hier noch wesentliche Abstractions (z.B. für den LinkManager) fehlen. Diese lassen sich recht einfach selbst erstellen, aber noch besser ist es auf Sitecore 8.2 zu aktualisieren, denn mit Sitecore 8.2 kommen mehr als 45 Abstractions, die wirklich fast alle statischen Sitecore API Aufrufe abdecken sollen.

Beispiel mit Glass-Mapper und Sitecore-Abstractions

Durch den Einsatz von den Sitecore Abstractions ändert sich die Implementierung des Beispiels wie folgt:

staticcodesample_withglassandsitecoreabstractions

Dadurch lässt sich nun der zugehörige Unit-Test wie folgt umsetzen:

staticcodesampletest_withglassandabstractions

 

Fazit

Durch den Einsatz der Sitecore Abstractions und des Glass-Mapper können mit Hilfe eines Mocking-Frameworks wie Moq einfach alle benötigten Werte in Unit-Tests setzen. Da es sich bei den Glass-Mapper Objekten um einfache Plain Old C# Objekte handelt können diese sogar direkt im Test eingesetzt und befüllt werden.

So macht das Schreiben von Unit-Tests gegen Sitecore auch wieder Spaß!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

*

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>