Archive for tag: test

Unittest af database og strengtest med contains

Jeg sad lige med en test der drillede. Det handlede om at jeg lave et opslag i min database med Linq to Sql, hvor kriteriet var noget i stil med 

q = q.Where(v => 
    v.Description.Contains(txt) 
    || v.Title.Contains(txt)
);

I min test testede jeg så på nogenlunde det samme kriterium, dvs. noget i stil med:

Assert.Istrue(
    actual.TrueForAll(v => 
        v.Description.Contains(txt) 
        || v.Title.Contains(txt)
    ));

Men dette fungerede ikke og min test fejlede!? Hm!

Efter noget roden frem og tilbage, kom jeg til at tænke på, at min database jo nok var sat op til at sammenligne strenge uden hensyntagen til forskellen mellem store og små bogstaver, hvilket jo ikke er tilfældet med String.Contains, der ser forskellen. 

Løsningen i min test blev derfor:

Assert.Istrue(
    actual.TrueForAll(v => 
        v.Description.IndexOf(txt, 
            StringComparison.InvariantCultureIgnoreCase) >= 0
        || v.Title.IndexOf(txt, 
            StringComparison.InvariantCultureIgnoreCase) >= 0
    ));

 Så passerer testen igen... :-)

Unittest af Data Access Layer

Jeg har på det seneste kastet mig ud i at teste Data Access Layer i et projekt. Projektet er baseret på SQL Server med Linq2Sql som ORM.

Jeg har længe udskudt denne aktivitet, da det virkede uoverkommeligt at skulle håndtere databasetilstand før og efter tests og har i det hele taget været i tvivl om hvordan jeg skulle gribe det an, med Linq2Sql som ORM. Nu har jeg taget tyren ved hornene og kastet mig ud i det. I den forbindelse har jeg gjort nogle foreløbige erfaringer som jeg lige vil lave et lille notat om...

Isolering af databasen

Som udgangspunkt vil jeg gerne have adskilt min testdatabase fra den database jeg benytter til at lave lokale udforskende tests på (dvs. de tests jeg laver når jeg kører det site jeg arbejder med). Dette er dels for ikke at fucke de data jeg sidder og laver mens jeg tester udforskende, dels for at kunne have "ren røv at trutte i", til mine automatiske tests.

Derfor har jeg replikeret skemaet fra min udviklingsdatabase og oprettet en dedikeret autotest-database, som jeg så peger på fra min testprojekts app.config.

Før hver test kører jeg en wipe af databasen. Dette gøres fra en metode dekoreret med AssemblyInititalize, hvor jeg udfører truncate/delete på alle relevante tabeller.

Derefter sætte jeg nogle basale data ind i databasen, da der er nogle data som skal være til stede for at systemet kan komme igang (f.eks. en bruger der kan refereres til fra mange andre tabeller).

Opbygning af tests

Når man så skal igang med at skrive tests, er det vigtigt at tænke på samtidigheden i udførslen af tests, hvilket bla. betyder, at man ikke altid kan være sikker på, hvilke data der ligger i databasen på et givet tidspunkt. Derfor kan tests ikke blindt gå ud fra at der er et specifikt antal elementer i databasen, da flere tests, på et vilkårligt tidspunkt i testforløbet, kan oprette eller slette data.

Tests bør såvidt muligt teste for tilstande, som er statiske fra gang til gang, dvs. hvis en funktion der returnerer en liste af elementer ud fra et eller andet kriterium, så bør man teste om dette kriterium er opfyldt i alle de returnerede elementer i stedet for at teste om et bestemt antal er returneret (med mindre det rent faktisk er et kriterium for testen).

Et eksempel på hvordan man kan gøre dette, kunne se således ud:

var target = new PersonSQLRepository(GetLocator());

List<IPerson> actual = target.GetPersonList(true);

Assert.IsTrue(actual.TrueForAll(p => p.Activated));

GetLocator er blot en funktion der returnerer en Service Locator, der benyttes til at oprette IPerson-objekter inde i PersonSQLRepository-klassen. Parameteren til GetPersonList angiver, om personen skal være aktiveret.

Hvis det f.eks. havde være en test af paginering af det returnerede data, ville det være OK at teste om antallet af elementer oversteg det ønskede antal og evt. om det totale antal gav anledning til at testen burde fejle.

Et eksempel på dette kunne se således ud:

var total = 0, 
    target = new PersonSQLRepository(GetLocator());

List<IPerson> actual = target.GetPersonList(10, 1, out total, true);

Assert.IsTrue(total>=10 ? actual.Count==10 : actual.Count==total);
Assert.IsTrue(actual.TrueForAll(p => p.Activated));

GetPersonList's parametre betyder: antal elementer der ønskes returneret, sidenr, antal relevante elementer ialt i databasen, samt kriteriet fra tidligere med kravet om aktiverede personer. Total indeholder, efter kaldet, det faktiske antal personer der møder kriteriet.

Som det ses tages der stilling til forskellige scenarier f.s.v.a. antal returnerede personer, hvilket her er OK, da kravene til værdisættet er statisk.

Opsummering

Ifm. med test af DAL bør databasen isoleres og man skal lave sine tests defensivt, dvs. ikke gøre antagelser om det datasæt man arbejder med, men blot forsøge at forholde sig tilstanden af de data der returneres holdt op mod de forventninger man har til data.

NCrunch til at motivere unittesting

Jeg har for nylig installeret NCrunch i min Visual Studio 2010 og det har været en booster for min lyst til at skrive unittests!

Hvor unittests før var noget jeg skrev, når jeg virkelig havde brug for at teste noget kompleks funktionalitet, er det, efter installationen af NCrunch, blevet motiveret af, at jeg hele tiden kan se, hvilke linjer i koden der ikke er dækket af en test, samt (næsten) øjeblikkeligt får feedback til linjer der indgår i tests der fejler.

NCrunch kører i baggrunden og udfører de tests som findes i den aktuelle solution. Dette gør at jeg kan skrives tests og kode funktionalitet uden at skulle tænke på hele tiden at builde mit projekt for at se om det jeg laver rent faktisk er syntaktisk korrekt og fungerer - det sørger NCrunch, i store træk, for at fortælle mig. Konsekvensen heraf er, at mit tidsforbrug er blevet mindre.

Der er dog nogle ting som er værd at overveje, nemlig at NCrunch i skrivende stund er et (gratis) betaprodukt, men at det givetvis kommer til at koste noget at bruge når det går ud af beta. Det er dog i version 1.38, så noget udvikling er der da foregået på produktet.

Selvom det kommer til at koste noget at bruge, tror jeg nu nok jeg kommer til at købe en licens til det, da det virkelig har ændret min holdning til at skrive unittests. Jeg kan anbefale at give det en chance, hvis du bare interesserer dig lidt for at skrive unittests, men synes det er bøvlet at bruge den indbyggede platform.

Tjek det ud på http://www.ncrunch.net/

 

Ninject med ASP.NET MVC 3

Jeg er for alvor begyndt at bevæge mig ind i .NET-udvikling og er i den forbindelse igang med at lære en masse nye begreber og metoder, som, for mange metoders vedkommende, ikke har givet så meget mening at arbejde ud fra i et ASP Classic miljø (mest fordi miljøet ikke understøtter værktøjerne til at gøre tingene på den måde).

Jeg tænker på områder som

  • SoC (Separation of Concern)
  • DI (Dependency Injection)
  • DRY (Don't Repeat Yourself)
  • Automatiserede tests

m.fl.

Jeg er klar over at man godt kan udøve ovenstående praksiser i ASP Classic (og jeg har da i nogen grad forsøgt dette gennem tiden), men da måden at inkludere kode på er yderst kluntet i mere komplekse scenarier og muligheden for at benytte objektorientering er begrænset, kræver det i bedste fald en yderst disciplineret udvikler at gennemføre dette. Desuden er muligheden for at isolere koden fra IIS begrænsede i ASP og derfor er unittests af kode som benytter session, response, request osv. besværlige i bedste fald.

Begreberne og metoderne er noget lettere at arbejde med i .NET, som i høj grad understøtter disse. Specielt når man arbejder med ASP.NET MVC.

En af de metoder jeg er blevet glad for er DI. DI tvinger mig til at tænke på tingene i mindre og mere afgrænsede opgaver for at få tingene til at hænge ordentlig sammen. DI øger også testbarheden af koden, hvilket understøtter et andet af ovenstående punkter.

DI kan gøres manuelt, men det kan hurtigt blive træls at skulle instantiere objekter alle de steder hvor man skal injicere funktionalitet, så derfor har jeg været på jagt efter et DI-framework, som kunne hjælpe med dette.

Jeg har fundet Ninject. Det eneste DI-framework jeg har prøvet, men jeg fornemmer det skiller sig ud fra mange andre DI-frameworks i.o.m. det ikke konfigureres i en XML-fil og dermed fjerner evt. fejl som følge at tastefejl i den tekstuelle XML. Det er i stedet konfigureret i kode og dermed bliver bindinger testet på compiletidspunktet, hvilket jo giver en tidligere mulighed for at fange evt. fejl. Dermed ikke sagt at det fjerner kørselsfejl ifm. instantiering, men det er en fejlkilde mindre ifht. konfiguration via XML.

Der findes et hav af extensions til Ninject, herunder til webforms, mvc3 og azure. Jeg har ikke selv arbejdet i meget andet end MVC 3 med ninject (ud over et lille testprojekt til webforms), så jeg har ikke den store erfaring med hvordan det fungerer i andre sammenhænge, men mon ikke det mest er et spørgsmål om opsætning. Resten kører nok ens, uanset projektets form, når først DI-frameworket er initialiseret.

Du kan tjekke ninject ud her: www.ninject.com