Integrationstestning av datalagret

Uppdatering 2008-10-09: Baserat på en kommentar från Fredrik Normén att det som det här inlägget berör inte är enhetstestning utan integrationstestning så har jag valt att uppdatera titeln. Skälet är att enhetstestning syftar till att testa “enheter” som alltså inte sträcker sig utanför klassgränsen, och så länge jag exempelvis använder mig av stubbar för att genomföra mina tester av datalagret så är jag alltså “ok”. Men när jag börjar gå mot externa datakällor (vilket berör i nedanstående inlägg) så lämnar jag området enhetstestning och börjar med “integrationstestning”. Dock så kan jag alltså använda samma “infrastruktur” och metod med hjälp av Visual Studio 2008’s inbyggda stöd för nedanstående. Läs gärna mer om enhetstester på wikipedia.

Det finns en hel del olika rekommendationer för hur du kan testa ditt datalager utan att påverka innehållet i databasen, och här försöker jag ge min “pitch” på ämnet. Som vanligt är jag nyfiken på hur du själv gör i dina projekt och vad som fungerar för dig.

Målet är alltså att kunna testa INSERT, DELETE och UPDATE mot datalagret (och mot databasen) utan att i slutändan påverka det som är lagrat där, eftersom vi inte vill påverka andra enhetstesters resultat eller om vi har en delad databas, andra personers tester. Det finns flera olika sätt att göra detta på, men det sätt som jag har fallit för är genom att använda transaktioner i .NET Framework.

I korthet går det ut på att skapa en transaktion innan respektive test-metod utförs och sedan göra en “rollback” på transaktionen efter att jag testat resultatet.image

För att hålla komplexiteten nere så har jag valt att hålla mitt “DAL-gränssnitt” enkelt och har helt enkelt tre metoder som jag vill skapa några tester för:

public interface IProductDAL {
    IList<Product> GetProductsByCategory(string category);
    bool InsertProduct(Product product);
    Product GetProductById(string productId);
}

Det behövs ett antal tester för att testa dessa metoder och få en hög nivå på “code-coverage” (för närvarande har jag 100%, och det känns “lagom” :) ). Exempelvis så har jag metoder för att testa “InsertProduct”:

  1. Lägg till en produkt
  2. Lägg till en produkt och verifiera att produkten finns i databasen
  3. Lägg till en produkt som redan finns i databasen och förvänta mig ett SqlException

En helt ok lösning på problemet med skräpdata i databasen är då att skapa ett TransactionScope innan varje metod och sedan göra en .Dispose efter metodens exekvering. Exempelvis genom att lägga till följande kod i min testklass:

private TransactionScope scope;

[TestInitialize]
public void InitializeTest()
{
    scope = new TransactionScope(TransactionScopeOption.RequiresNew);
}

[TestCleanup]
public void CleanUpTest()
{
    scope.Dispose();
}

Efter att ovanstående kod är inlagd i klassen så kommer dessa metoder att anropas före och efter varje test och se till så att transaktionen rullar tillbaka.

Ett alternativ som kan tyckas vara ännu grannare är Justin Burtsch’s lösning genom att skapa ett [Rollback]-attribut som kan läggas till på de test-metoder som ska exekveras inom en transaktion. Det är en snygg lösning men med lite krav på att testklassen måste ärva från en speciell basklass och att initialisering och uppstädning (TestInitialize och TestCleanUp) måste hanteras med hjälp av andra metoder. Det sista betyder att om du vill bryta ut logik från dina testmetoder för att exempelvis skapa instanser av variabler osv måste lägga detta i en OnInitialize metod istället, små justeringar, men ändå lite annorlunda beteende.

Vill du läsa mer om ämnet så finns Roy Osheroves blog (han kallar den för ISerializable) som innehåller en del inlägg i ämnet.