Test jouw SharePoint code met Pex&Moles

Unit testing wordt door steeds meer ontwikkelaars gebruikt om hun code te testen. Recent onderzoek bij Microsoft heeft aangegeven dat 79% van de ontwikkelaars unit tests schrijven. Voor een SharePoint ontwikkelaar is het over het algemeen lastig om unit tests te schrijven. Veel methodes en objecten leunen op een internal of sealed class in een van de binaries van SharePoint. Het ontwikkelen voor SharePoint vereist dan eigenlijk altijd een beschikbare test omgeving om de geschreven code te testen. Pex & Moles is ontwikkeld om ontwikkelaars te assisteren bij het schrijven van tests. Met Pex kunnen we geparameteriseerde unit tests genereren vanuit onze code. Met Moles kunnen we de code die wordt getest isoleren van zijn afhankelijkheden waardoor we doelgericht kunnen testen.

Pex

De kracht en het gebruik van Pex is het best uit te leggen met een voorbeeld. In dit voorbeeld (Listing 1) hebben we een simpele class met 1 methode. Deze methode geeft op basis van een enum een string terug. Als we voor deze methode nu een test zouden schrijven en we willen alle cases in de switch raken dan zouden we waarschijnlijk 4 tests schrijven. Nu is dat in dit voorbeeld niet zo problematisch maar hoe complexer de code, hoe meer tests we moeten schrijven om alle branches van de code te testen.

public enum Usage { Pex, Moles, PexAndMoles}
 
public class UnitTestUsage
{
 public static string DeferUsage(Usage usage)
 {
    switch (usage)
    {
      case Usage.Pex:
      {
        return "Pex";
      }
      case Usage.Moles:
      {
        return "Moles";
      }
      case Usage.PexAndMoles:
      {
        return "PexAndMole";
      }
      default:
      {
        throw new ArgumentException("no usage has been defined");
      }
    }
 }
}
Listing 1: Setup van Demo Code. (dit is geen productie code of best practice)

Een oplossing voor deze uitdaging is een geparameteriseerde unit test die de complexiteit van de unit test reduceert. Pex is mede ontwikkeld om deze geparameteriseerde unit test te genereren vanuit jouw code. Je kunt met een muis klik de unit test genereren.

Figure 1: Genereer een test met Pex

Het resultaat van deze actie na een paar kleine bewerkingen is te zien in listing 2.

[PexClass(typeof(UnitTestUsage))]
[PexAllowedExceptionFromTypeUnderTest(typeof(InvalidOperationException))]
[PexAllowedExceptionFromTypeUnderTest(typeof(ArgumentException)]
[TestClass]
public partial class UnitTestUsageTest
{
 /// <summary>Test stub for DeferUsage(Usage)</summary>
 [PexMethod]
 public string DeferUsage(Usage usage)
 {
    //arrange (noting to arrange
 
    //act
    string result = UnitTestUsage.DeferUsage(usage);
         
    //assert
    Assert.AreEqual<string>(Enum.GetName(typeof(Usage), usage), result);
 
    return result;
 }
}
Listing 2: Een test die gegenereerd is door Pex (met enkele wijzigingen)

De Assert.AreEqual die in de test is geschreven wordt niet door Pex gegenereerd maar moet door de ontwikkelaar zelf worden aangevuld. Pex weet niets over de correctheid van de resultaten, het kan alleen input geven die ervoor zorgt dat zoveel mogelijk branches van de code worden getest. Als alle checks op de data zijn geschreven kunnen we de test draaien. Normaal gesproken zouden we de test opzoeken in de test list editor van Visual Studio en dan de test selecteren. Omdat we deze test met Pex hebben gemaakt vinden we deze test niet terug in de lijst. We kunnen deze test draaien door in de test met de rechtermuisknop het context menu op te vragen. In het context menu is een optie die Run Pex Explorations heet. Hiermee geven we Pex de opdracht om onze geparameteriseerde test te draaien met input die ervoor zorgt dat zo veel mogelijk branches worden getest. In ons voorbeeld geeft Pex ons 4 test cases die allemaal een andere branch raken in onze code.

Figure 2: Resultaten van Pex
#JPG Caron_Pex_Moles_02.jpg

Zoals in figuur 2 te zien is heeft Pex alle opties van de Enum gebruikt en heeft hij ook een ongeldige enum waarde toegevoegd (4). Deze waarde zorgt ervoor dat de default case uit onze switch wordt uitgevoerd. Opvallend is dat de ongeldige waarde een succesvolle test geeft en dat de waarde Pex en Moles faalt. De test met de ongeldige waarde geeft een ArgumentException en die staat in onze verwachte excepties van de testclass. De test die faalt, geeft aan dat de string die wordt terug gegeven door onze originele methode niet overeenkomt met de string die verwacht wordt. Als we nog eens goed naar de code kijken dan zien we dat er een typefout staat in onze methode. We kunnen de fout herstellen en Pex opnieuw vragen om de tests te draaien. Nu faalt geen enkele test en hebben we onze methode volledig getest. De vier (niet geparameteriseerde) tests die zijn aangemaakt door Pex zijn wel benaderbaar via de test list editor van Visual Studio waardoor je de tests ook afzonderlijk kan draaien.

Moles

In ons vorige voorbeeld hadden we een erg simpel scenario waarbij de code geen afhankelijkheden had naar andere bronnen. In SharePoint ontwikkeling is dit echter zelden het geval. Voor het ontwikkelen van onderdelen voor SharePoint worden vaak gedeeltes van de API gebruikt. Deze API heeft echter een omgeving nodig om daadwerkelijk te kunnen werken. Dit maakt het testen van jouw code lastig omdat we geen SharePoint omgeving kunnen nabootsen in een unit test. Moles geeft ons de mogelijkheid om alle communicatie over en weer met de SharePoint API om te leiden via een Mole.

Een Mole is een class die als wrapper dient waarmee je elke .NET methode kunt omleiden naar een delegate. Alle classes die zijn gegenereerd krijgen de prefix M mee, de mole van SPWeb zal dus MSPWeb zijn.

Door een Mole van SPWeb in onze Unit test op te nemen zeggen we eigenlijk tegen de runtime dat we een instantie van SPWeb willen vervangen voor de definitie van een Mole. In Listing 3 is een code voorbeeld opgenomen die een aantal objecten uit de SharePoint API gebruikt.

public static void WriteWebDataToConsole(SPItemEventProperties props)
{
 SPWeb web = props.OpenWeb();
 if(web != null)
 {
    Console.WriteLine(“Name: {0}”,web.Name);
    Console.WriteLine(“Master Url: {0}”,web.MasterUrl);
    Console.WriteLine(“Url: {0}”,web.Url);
 
    SPList list = props.List;
    Console.WriteLine(“Default View: {0}”,list.DefaultViewUrl);
 
    SPListItem item = list.GetItemById(props.ListItemId);
    Console.WriteLine(“Item Name: {0}”,item.Name);
 
 }
}
Listing 3: Setup van SharePoint Demo Code. (dit is geen productie code of best practice)

Belangrijk: Deze code is alleen voor het demonstreren van Moles in combinatie met de SharePoint API, het is onder geen enkele omstandigheid code die in productie gebruikt kan worden. In het voorbeeld zien we een SPWeb object waaruit een SPList wordt opgevraagd. Er wordt uit de lijst nog een SPListItem opgehaald. Als we WriteWebDataToConsole aanroepen vanuit een normale Unit Test dan zal de test falen omdat de test hoogst waarschijnlijk geen SharePoint installatie zal vinden voor de API. We kunnen dit echter voorkomen door het gebruik van Moles. Om te beginnen met het inzetten van moles moeten we de Microsoft.SharePoint.Moles.dll linken aan ons test project. Een uitgebreide uitleg over het compilen van deze bestanden is te vinden in de Moles documentatie.

[PexMethod]
Public void Test(Guid listId)
{
 var properties = new MSPItemEventProperties();
 
 SPItemEventProperties runtimeProperties = properties.Instance;
 
 UnitTestUsage.WriteWebDataToConsole(runtimeProperties);
}
Listing 4: Eerste opzet om te beginnen met het inzetten van Moles.

In de eerste regel definiëren we de Mole van SPItemEventProperties, MSPItemEventProperties. Dit geeft ons de mogelijkheid om de input van onze functie te bepalen. Een mole heeft altijd een referentie naar het originele object SPItemEventProperties. Dit object kun je met de property Instance opvragen. We openen het context menu weer en kiezen run pex exploration. Pex gaat proberen de test te draaien en daarbij zal het de Mole van SPItemeEventProperties gebruiken in plaats van een regulier object. In dit specifieke geval zal Pex een fout geven: MoleNotImplementedException. Dit betekent dat er voor een onderdeel van de mole geen specificatie is. Als we kijken naar de code dan zien we deze code: SPWeb web = props.OpenWeb(). Deze methode geeft een SPWeb object terug maar omdat we met een mole werken in plaast van een API object weet Moles niet wat het met deze functie moet doen. We kunnen dit gedrag in onze test beschrijven, door de functie te herdefiniëren met een delegate. Moles zal dan de OpenWeb vervangen voor onze definitie van OpenWeb. Door herhaaldelijk de pex exploration uit te voeren, vinden we alle definities die we moeten herdefiniëren. Uiteindelijk komen we uit bij een Mole zoals in Listing 5

[PexMethod]
Public void Test(Guid listId)
{
 var properties = new MSPItemEventProperties()
 {
    OpenWeb = () => {
        return new MSPWeb()
        {
          NameGet = () => {return @”Pex & Moles - SDN”;},
          UrlGet = () => { return @”http://sdn.nl”;},
          MasterUrlGet = () => { return @”http://microsoft.nl”;},
          ListsGet = () => new MSPListCollection()
          {
            ItemGetGuid = id => new MSPList()
            {
              GetItemByIdInt32 = (itemId) =>
              {
                return new MSPListItem()
                {
                  NameGet = () => {return “SDN Item”;}
                };
              }
            },
            DefaultViewUrlGet = () => { return @”/list/unittests/”;}
          }
        };
     },
     ListIdGet = () => listId,
     ListItemIdGet = () => 1,
 };
 
 SPItemEventProperties runtimeProperties = properties.Instance;
 
 UnitTestUsage.WriteWebDataToConsole(runtimeProperties);
}
Listing 5: Complete Mole voor onze SharePoint code

We kunnen dus met een geneste notatie alle methodes en properties een nieuwe betekenis geven. Op deze manier is onze code te isoleren van zijn afhankelijkheden. Hoe complexer de code is die je test aanroept, hoe complexer de mole zal worden. Dit is over het algemeen een teken dat de code gerefactored moet worden. We kunnen er ook voor kiezen om een behaviour modelleren.

Behaviours

Hoewel Moles een goede aanpak lijkt zijn er ook een aantal nadelen. Zoals te zien is aan de code, kan het inzetten van een Mole complex worden. Het schrijven van de unit test moet uiteraard niet een doel op zich zijn. Daarnaast kan een simpele wijziging in de code ervoor zorgen dat we een aanpassing moeten doen aan de Mole. Als we kijken naar de volgende twee regels dan lijkt dat op het oog dezelfde code maar toch vereist het een andere implementatie van de Mole.

 
SPListItem item = list.GetItemById(props.ListItemId);
SPListItem item2 = list.Items[props.ListItemId];
 
Listing 6: Nagenoeg zelfde code, Andere Mole nodig

De oplossing voor het gebruik van Moles voor SharePoint wordt gelukkig aangereikt door de makers van Moles. Bij de installatie van Pex en Moles worden een aantal projecten meegeleverd. Als deze projecten gebuild worden dan worden er een aantal moles bestanden en dll’s aangemaakt. Deze binaries bevatten behaved types. Een behaved type is een wrapper om een mole die een state definieert voor het gedrag van een bepaalde omgeving. Deze types zijn herbruikbaar en dynamisch waardoor ze minder vatbaar zijn voor veranderingen in de code. Het werken met Behaviours vergt echter meer hands-on kennis over pex en moles en zijn in mijn optiek nog niet vergenoeg uitgewerkt om er serieus mee aan de slag te gaan.

Conclusie

We kunnen met aangrenzende zekerheid zeggen dat Pex & Moles een toevoeging is voor het testen van code. De tests hebben als doel om een zo goed mogelijke code coverage te behalen. Daarnaast zullen we eerder tegen excepties aanlopen waardoor we stabielere code kunnen opleveren. Code die afhankelijk is van de SharePoint API kunnen we testen via de Moles en eventueel bijgeleverde Behaviours. De behaviours zijn echter nog niet compleet waardoor je vrij snel tegen de beperking aan zal lopen. Het belooft echter veel goeds voor de toekomst en het zal het testen van SharePoint code vereenvoudigen. Interessante video’s zijn te vinden op http://channel9.msdn.com, en op de homepage van het Pex & Moles project http://research.microsoft.com/en-us/projects/pex/

Geef feedback:

CAPTCHA image
Vul de bovenstaande code hieronder in
Verzend Commentaar