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.

Comment