ASP.Net MVC2: Met eenvoudig testbare database laag

Sinds enkele weken ben ik bezig met het bouwen van een database applicatie
met behulp van ASP.Net MVC2. Hoewel ik een hoge kennisdrempel verwachtte, bleek dit enorm mee te vallen. De leercurve is eigenlijk best vlak.

Als ik terugkijk op die weken dan moet ik constateren dat het loslaten van
ingesleten ASP.Net gewoontes het moeilijkste was. MVC2 in zijn huidige vorm kent niet of nauwelijks visuele controls dus de VS2010 toolbox kan gesloten blijven. Ook UserControls zijn in eerste instantie niet aan de orde. Wat overblijft, is het plaatsen van ASPX pagina’s in de Solution. Deze pagina’s zijn gevuld met
HTML en MVC2 specifieke statements om doorgegeven data uit te lezen en te
tonen.

TestDrive

Nu wil ik niet beweren dat MVC2 de ‘silver bullet’ is, in tegendeel. Maar
MVC2 neemt een ruime plaats in tussen ‘klassiek’ ASP.Net en de Dynamic Websites . Ook leeft het gewoon naast de technieken rond SharePoint. Maar voor mijn gevoel geeft MVC de productiviteit terug aan de ontwikkelaars welke gewoon op een prettige manier CRUD applicaties in .Net willen bouwen.

Want MVC2 werkt prima samen met het Entitiy Framework (EF2). Deze ORM mapper heeft ook een evolutie doorgemaakt en sluit daardoor nu prettig aan bij MVC2. EF4 biedt oa. de mogelijk tot lazy loading wat weer tot het sneller bouwen van een Master Detail scherm leidt. Voorbeeldje: Als je een ProductOrder in een View kunt tonen, dan heb je binnen de View automatisch ook de beschikking over het gerelateerde Product, de Klant en de OrderRegels. Je hoeft dus niet direct een gespecialiseerde ViewModel samen te stellen.

Ook is het MVC als concept voorbereid op Unittesting. Hier wordt door de
collega’s al hoog over opgegeven dus ik sluit me hier gewoon bij aan. Maar wat
minder vaak geroepen wordt, is de mogelijkheid tot het mocken van de database
toegang. Dit is standaard niet via de T4 generatie in de controllers gegenereerd maar met slechts een paar handelingen is dit te realiseren. Hoe te beginnen? Uitgangspunt is dat in Controllers geen enkele LinqToSql statement geplaatst wordt. Ook mag de Controller niet direct de EF4 context bezitten als private member.

Deze verantwoording wordt namelijk overgelaten aan een apart te instanciëren
klasse, genaamd Repository. Deze kan via LinQ de EF4 context aanroepen en deze queries als publieke methodes aanbieden. Van de repository klass kan een
interface afgeleid worden, genaamd IRepository, welke de publieke methodes
beschikbaar stelt als contract.

public class ContactRepository : IContactRepository
{
  // Put your EF context here
  private Context _Context = new Context();

  public List<Contact> List(string filter)
  {
    // Do your LinQ magic here
    return null;
  }
}

public interface IContactRepository
{
  List<Contact> List(string filter);
}

De magie is om op de Controller een private field aan te maken van
het type IRepository. Via een extra Constructor met een IRepository parameter
kan dan een instantie van IRepository doorgegeven worden. Deze Constructor is
voor de unittests. Want via een Mocking framework (Mock-You)
kan dan een IRepository mock gegenereerd worden.

public class ContactController
{
  private IContactRepository _ContactRepository;

  public ContactController() : this(new ContactRepository())
  {
  }

  public ContactController(IContactRepository contactRepository)
  {
    _ContactRepository = contactRepository;
  }

  public ViewResult Index()
  {
    return View(_ContactRepository.List());
  }
}

[TestMethod]
public void TestMethod_Contact_List()
{
  var mock = new Mock<IContactRepository>();
  mock.Setup(framework => framework.List("Sander"))
    .Returns(new List<Contact>() {new Contact(){Naam="Alex"}})
    .AtMostOnce();
  IContactRepository contactRepository = mock.Object;
  List<Contact> contacts = contactRepository.List("Alex");
  Assert.IsNotNull(contacts);
  Assert.AreEqual(1, contacts.Count);
  mock.Verify(framework => framework.List("Alex"));
}

Maar runtime moet de repository klasse aangeroepen worden. Plaats
dus op de Controller ook een standaard Constructor (zonder parameters) welke
simpelweg de repository klasse instancieërt en in de private member plaatst. Doe
dit bij voorkeur via het doorlinken naar de eerdere genoemde Constructor met de parameter via de this statement.

Met deze eenvoudige uitbreiding op het MVC framework worden controllers
nog beter testbaar. Wil je dus weten of jouw controllers goed testbaar zijn?
Controleer dan de app.config van het unittest project. Daar mag niet de database
ConnectionString aan toegevoegd is.

Wil je eens starten met MVC2? Negeer de cheesy titel en bekijk eens Nerd
Dinner
. Dit geeft snel en goed een overzicht van wat MVC2 inhoudt.

Advertenties

Eenvoudig unittesten voor Sql Server Integration Services

Sql Server Integration Services (SSIS) is het antwoord van Microsoft op de
vraag naar ETL. ETL staat voor Extract, Transform en Load. Eigenlijk wordt er
gewoon met data geschoven, van bron(-nen) naar doel(-en).

Mocht binnen een project veel met data import gewerkt worden dan kan SSIS vrijwel zeker een hele hoop problemen oplossen. SSIS geeft de mogelijkheid om bronnen en doelen te definiëren waarna een workflow ontworpen wordt om de data te extraheren, te valideren, te transformeren en weer weg te schrijven.

Input Output

Collega’s welke met BizTalk werken komt dit bekent voor maar daar is tevens
een workflow engine aanwezig, compleet met storage. Echter voor SSIS is de
workflow slechts een middel om data te transformeren. Door de grafische
representatie en eenvoudige maar krachtige scripting is SSIS ook een stuk
basisgereedschap voor administrators.

Er is een leerkurve om met SSIS aan de gang te gaan maar hier en hier staat voldoende om er eens mee te beginnen.

Een stuk SSIS logica (package) kan zowel direct in Sql Server bewaard worden,
maar ook op schijf opgeslagen worden.  Zo’n package krijgt de extensie DTSX.
Hoewel opslag op schijf de package kwetsbaar maakt voor systeem-afhankelijke
paden, heeft het wel degelijk voordelen. De package wordt zo makkelijk
transportabel in een OTAP omgeving met verschillende SQL servers en dit maakt de package ook beschikbaar voor unittesten.

Want soms lijkt iets heel lastig maar is het in wezen juist er simpel. Binnen
ons team zijn wij met de bouw van Business Intelligence Software (BIS) begonnen waarbij SSIS de basis is voor de import van data en bestanden uit csv bestanden, mappen en webservices. En hierbij kwam al snel de vraag hoe we konden bewijzen dat onze import met verschillende scenario’s kon omgaan.

Uiteindelijk bleek de oplossing heel eenvoudig te zijn. Voor SSIS is ook een
.Net API beschikbaar. Het is hiermee zelfs mogelijk om SSIS packages volledig
uit te coderen maar daar waren wij niet in geïnteresseerd. Wel is het mogelijk
om zo via code een package te laden, de variabelen te vullen en uit te voeren.
Vervolgens is te controleren of de package correct uitgevoerd is. Uiteindelijk
kan gecontroleerd worden of de package correct gereageerd heeft op de
gecontroleerde input.

Hoewel dit meer een integratie test is omdat het mocken van services
en databases hierbij wat lastig is, scheelt deze aanpak veel tijd voor het
ontwikkelteam. Anders moet tijdens het coderen steeds handmatig een scenario
afgewerkt worden en bij iedere wijziging mogen die weer opnieuw gecontroleerd
worden.

Maak dus naast het SSIS project een unittest project aan en voeg
onderstaande references toe.

SSIS API Assemblies

Maak vervolgens een unittest aan welke de package in het geheugen laadt.

In onderstaande voorbeeld wordt eerst een csv uitgelezen. Vervolgens wordt
een sommatie weggeschreven in een ander bestand. Bus beide bestandsnamen worden opgegeven bij de package.

using System;using System.Text;using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Pipeline;
using SSISRumtime = Microsoft.SqlServer.Dts.Runtime.Wrapper;
using System.IO;
namespace TestProject
{
  [TestClass]
  public class UnitTest1
  {

  [TestMethod]
  public void TestMethod1()
  {
    Application application = new Application();
    Package package = null;
    try
    {
      string inputFile =
        @"C:\TMP\Integration Services
        Project\TestProject\InputFiles\Test1.txt";
      string outputFile =
        @"C:\TMP\Integration Services
        Project\TestProject\OutputFiles\Test1.txt";
      package = application.LoadPackage(
        @"C:\TMP\Integration Services Project\Integration Services
        Project\bin\Package.dtsx", null);
      package.Connections["ImportTest"].ConnectionString =
        inputFile;
      package.Connections["ImportResult"].ConnectionString =
        outputFile;
      Microsoft.SqlServer.Dts.Runtime.DTSExecResult results =
        package.Execute();

      Assert.AreEqual(Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
        results, "Execution of package failed");
      Assert.IsTrue(File.Exists(outputFile));
      string outputTextExpected =
        "<html><body>1000</body></html>";
      string outputTextActual = "";
      using (StreamReader streamReader =
                           File.OpenText(outputFile))
      {
        outputTextActual = streamReader.ReadToEnd();
      }
      Assert.AreEqual(outputTextExpected, outputTextActual);
    }
    finally
    {
      package.Dispose();
      package = null;
    }
  }
}
}

Omdat we weten wat we als invoer meegeven, weten we ook wat we mogen
verwachten.

We roepen dus Execute op de package aan en controleren of de succesvol is
uitgevoerd. Maar we moeten wel het resultaat ook controleren. Daarom wordt
uiteindelijk het doelbestand uitgelezen en op inhoud vergeleken.

SSIS Unittest passed

Uiteindelijk levert deze test een groen vinkje op. Yes! (en vieren met een
kopje koffie, want: “Vier je successen”).

Dit geeft een aardige start.

Mocht dit  uiteindelijk als unittest mechanisme te licht bevonden worden, dan
zijn er ook zwaardere middelen aanwezig.