In Teil 1 habe ich das Grundprinzip des Fakes Frakework dargestellt. In diesem Teil gehe ich nun auf Tests für SharePoint ein.
Zunächst muss ein Testprojekt hinzugefügt werden und die Shim Klassen für die SharePoint.dll erstellt werden. Dieser Vorgang kann durchaus ein paar Minuten dauern.
ein einfaches Demo
angenommen wir möchten folgenden Code testen:
1: public void UpdateTitle(string neuerTitel)
2: {
3: SPWeb web = SPContext.Current.Web;
4: web.Title = neuerTitel ;
5: web.Update();
6: }
Diese 3 Zeilen enthalten keinen komplizierten Code. Aber es verstecken sich einige Aufrufe von SharePoint Klassen. Diese müssen mit dem Fakes Framework “simuliert” werden.
Wir haben den Zugriff auf den Context, oder genauer gesagt auf das Web des Contexts und dann einen schreibenden Zugriff auf die Title Eigenschaft des Webs. Ein Unittest muss nun prüfen, dass dem Title-Property ein neuer Wert zugewiesen wurde.
Die Testmethode enthält folgenden Code:
1: [TestMethod]
2: public void SimpleUT_Backup()
3: {
4: using (ShimsContext.Create())
5: {
6: string titel = "";
7:
8: ShimSPContext.CurrentGet = () =>
9: {
10: return new ShimSPContext()
11: {
12: WebGet = () =>
13: {
14: return new ShimSPWeb()
15: {
16: TitleSetString = (wert) =>
17: {
18: titel = wert;
19: }
20: };
21: }
22: };
23: };
24:
25: SimpleDemos demo = new SimpleDemos();
26: demo.UpdateTitle("NeuerTitel");
27:
28: Assert.AreEqual("NeuerTitel", titel);
29:
30: }
31: }
In Zeile 8 wird die Get-Methode des SPContext.Current mit einem neuen Objekt (ShimSPContext) bestückt und in Zeile 12 wird ein ShimObjekt für das Web-Objekt gebildet. Die Set-Methode des Web Objektes wird in Zeile 16 mit einer Methode hinterlegt. In dieser wird der übergebene Titel-Wert in eine Variable gespeichert. (Zeile 18)
Nach diesen Vorbereitungsarbeiten (Arrange) wird die zu testende Methode aufgerufen (Action) und in Zeile 28 wird geprüft, dass der Wert auch wirklich gesetzt wurde (Assert).
Dieses simple Beispiel zeigt, dass für alle verwendeten Methoden und Objekte von SharePoint Shim-Klassen mit deren Methoden gebildet werden müssen.
ein leicht “komplizierter” Code in SharePoint
Meist sind die Code-Teile in SharePoint nicht so simpel wie im ersten Beispiel. Bei längerem Code kann die Anzahl an notwendigen Shim-Objekten leicht eine Anzahl erreichen, wo man mit obiger Methode nicht weiterkommt.
Im Fakes-Framework gab es eine Sammlung an Behavior-Klassen, die bereits die wichtigsten Funktionen von SharePoint in Fake-Klassen darstellten. Als Unit-Tester musst man nicht bei 0 beginnen. Diese Behavior-Klassen vermisse ich noch.
Im zweiten Beispiel möchte ich einen EventReceiver testen:
1: public override void EmailReceived(SPList list, SPEmailMessage emailMessage, String receiverData)
2: {
3: if (emailMessage.PlainTextBody.Contains("Anmeldung"))
4: {
5: SPList aufgabenListe = list.ParentWeb.Lists["Aufgaben"];
6: SPListItem neueAufgabe = aufgabenListe.AddItem();
7: neueAufgabe["Title"] = "Anmeldung prüfen";
8: neueAufgabe.Update();
9: }
10:
11: base.EmailReceived(list, emailMessage, receiverData);
12: }
In diesem Eventreceiver, soll bei jeden Eingang eines Emails, das im Betreff “Anmeldung” beinhaltet, eine Aufgabe anlegt werden. Um dies zu testen, benötigen wir eine Listen-Klasse, die überprüfen kann ob ein Item angelegt wurde.
Da dies im SharePoint Development öfter vorkommen kann, habe ich begonnen diese häufig auftretenden Aufgaben in eigenen Mock-Klassen zu implementieren. Und erreiche so eine höhere Wiederverwendbarkeit meiner Tests.
Um eine Liste testen zu können wird ein SPListItemMock und ein SPListMock benötigt. Das List Item ist wie folgt implementiert:
1: class SPListItemMock
2: {
3: public Dictionary<string, string> FeldWerte = new Dictionary<string, string>();
4: public ShimSPListItem theItem;
5: public bool UpdateCalled = false;
6:
7: public SPListItemMock()
8: {
9: theItem = new ShimSPListItem();
10: theItem.ItemGetString = (feld) =>
11: {
12: return FeldWerte[feld];
13: };
14: theItem.ItemSetStringObject = (feld, wert) =>
15: {
16: FeldWerte.Add(feld, wert.ToString());
17: };
18: theItem.Update = () =>
19: {
20: UpdateCalled = true;
21: };
22: }
23:
24: public static implicit operator SPListItem(SPListItemMock m)
25: {
26: return m.theItem;
27: }
28: }
Die Klasse beinhaltet ein Dictionary um die FeldWerte des Items zu speichern. Es ist Public definiert um in einer Testmethode auf die gespeicherten Werte zugreifen zu können oder vor einem Test entsprechend zu setzen.
Außerdem wird in der Klasse das Shim-Objekt für ein SPListItem angelegt und die Getter und Setter implementiert. Die Updatemethode wurde ebenfalls implementiert. Wenn diese aufgerufen wird, so wird der Aufruf in einer Bool-Variable hinterlegt.
Zu guter Letzt wird noch im Cast-Operator implementiert. Das erleichtert in der Testmethode den Zugriff auf das ShimObjekt.
Die SPListMock Klasse ist analog aufgebaut:
1: class SPListMock
2: {
3: public ShimSPList theList;
4: public List<SPListItemMock> items = new List<SPListItemMock>();
5: public SPWebMock ParentWeb;
6:
7: public SPListMock()
8: {
9: theList = new ShimSPList();
10: theList.AddItem = () =>
11: {
12: var addedItem = new SPListItemMock();
13: items.Add(addedItem);
14: return addedItem;
15: };
16:
17: theList.ParentWebGet = () => ParentWeb;
18:
19: }
20:
21: public static implicit operator SPList(SPListMock m)
22: {
23: return m.theList;
24: }
25: }
Nun zur Testmethode:
Diese ist im Vergleich zum Einstiegsbeispiel recht simpel und übersichtlich aufgebaut:
1: [TestMethod]
2: public void EventReceiver_Test_mitMocks()
3: {
4: using (ShimsContext.Create())
5: {
6: ShimSPEmailMessage msg = new ShimSPEmailMessage()
7: {
8: PlainTextBodyGet = () => "Anmeldung zur SharePoint Konferenz in Wien"
9: };
10:
11: SPWebMock web = new SPWebMock();
12: SPListMock liste = new SPListMock();
13: web.Listen.Add("Aufgaben", liste);
14: liste.ParentWeb = web;
15:
16: AnmeldungsEmailEventReceiver target = new AnmeldungsEmailEventReceiver();
17: target.EmailReceived(liste, msg, "");
18:
19: Assert.AreEqual("Anmeldung prüfen", liste.items[0].FeldWerte["Title"]);
20: Assert.IsTrue(liste.items[0].UpdateCalled);
21: }
22:
23:
24:
25: }
Durch die Mockklassen wird die Verwendung der Shim-Klassen einfacher. In Zeile 11 beginnen wir die notwendigen Klassen vorzubereiten. Ein Web und eine Listen-Klasse.
In Zeile 16 wird der zu testende Code ausgeführt. Und ab Zeile 19 wird überprüft, dass in der Liste ein entsprechender Eintrag geschrieben wurden.
Fazit
Das Fakes-Framework bietet die Möglichkeit jedes beliebige .NET Assembly zu “faken” und die Methoden durch eigene Methoden auszutauschen. Es kann auch auf die SharePoint-Assemblies angewandt werden. Allerdings ist einiges an wiederholenden Code zu erstellen. Hier hilft hoffentlich die Idee eigene Mock-Klassen zu erstellen weiter. Diese Klassen sind nur einmal zu erstellen und können bei jedem Test wiederverwendet werden.