Test driven development – my two cents.

by DotNetNerd 11. May 2008 13:07

Test driven development – my two cents.

Unit testing er en metode til at teste at en individuel enhed kildekode fungerer korrekt.  Egentlig har unit testing været brugt i flere årtier, men begrebet har den seneste tid udviklet sig til lige at være et buzzword der er oppe i tiden – især som metodik i form af Test Driven Development. TDD bliver der blogget enormt meget om for tiden – og netop derfor vil jeg ikke kaste mig ud i at forklare hvad det er, men lader det være en forudsætning for at læse videre.

Udbredelsen af TDD/unit testing er en stor del af grundlaget for at Microsoft har kastet sig over at lave MVC frameworket, som et alternativ til den klassiske måde at udvikle ASP.NET applikationer på.

Rob Conery der står bag SubSonic, og nu er ansat hos Microsoft er for tiden i gang med en serie på sin blog som han kalder MVC storefront, hvor han går efter at blive klogere på både MVC og TDD. En sjov detalje han har bemærket er at alle i miljøet snakker om ”at hvis man nu er purist skulle man gøre sådan og sådan”, men han har umiddelbart ikke kunne finde nogen der direkte er purister inden for TDD.

Personligt kan jeg godt se anvendeligheden i TDD, men jeg mener helt klart at der er alt for meget hype om det, og at det bestemt ikke er alting det er velegnet til. Min holdning skyldes helt enkelt to ting; for det første at man ofte ikke har en god nok beskrivelse af hvad man skal udvikle til at man kan skrive en rigtigt god unit test, og for det andet at det ikke altid er noget godt kvalitetsmål. Med det mener jeg at det ikke er alting man kan teste ved hjælp af unit tests, både fordi eksempelvis usability slet ikke er noget mål for en unit test, men også fordi omkostningerne ved at skrive og vedligeholde tests kan være større end gevinsten. Dette er i nogen grad en pointe ”Joel on software” også har fremført, blandt andet ved en tale på Yale.

Jeg laver selv en del applikationer, hvor beskrivelserne er sparsomme, og udviklingen derfor ikke kan undgå at blive ”trial and error” baseret. Ikke forstået som i at der er masser af ”fejl” som sådan i koden (selvfølgelig er der også det), men fordi kundens ønsker ændrer sig markant i løbet af processen. Teoretikeren vil nok sige at så er analysen af projektet ikke god nok, en pointe der ikke går helt tabt på mig, men der er mange kunder hvor det simpelthen ikke er muligt i praksis. Let the religious battle between theory and pragma begin!

Min anden pointe med at det ikke er alting der lader sig unit teste, er selvfølgelig også et spørgsmål om vilje. Brugerflader og sprocs er vanskelige at teste, og selvom man gør sig en masse anstrengelser for at lave unit tests af disse ting er værdien mindre - i forhold til at kunne teste ”rene funktioner”, med kendt input og output. En sidste observation jeg har gjort mig, er at fejl oftere skyldes integrationsproblemer end direkte logiske fejl – og i systemer hvor det er tilfældet er integrationstests i virkeligheden mere formålstjenlige.

Selvom jeg måske nu lyder meget skeptisk, synes jeg at unit tests og TDD er rigtigt godt, til de ting det rent faktisk er opfundet til. Min indsigelse er blot imod den megen hype der er, da heller ikke det er nogen Silver bullet, og jeg føler at anvendeligheden af unit tests har det med at blive overdrevet.

Frameworks og tools – de små måske vigtige forskelle.

De fleste jeg har snakket med der har arbejdet med unit testing kender NUnit frameworket, som nok er det mest udbredte framework til unit testing i .NET. Frameworket er oprindelig portet fra JUnit og har efterhånden en del år på bagen.

Der findes imidlertid også to andre udbredte open source  alternativer som er MbUnit og XUnit – forskellene imellem de tre frameworks er relativt små, men som man siger ”god is in the details”.

MbUnit omtales som NUnit på crack, og er et framework der er udviklet til at være mere udvidelsesvenligt end NUnit er. Det er i dag nok det mest advancerede unit testing framework og giver blandt andet mulighed for at man via attributter beskriver data der skal passes til en testmetode, og derved kan den samme metode kaldes flere gange hvor forskellige grænseværdier testes.

[RowTest]
[Row("Christian", "123")]
[Row("Christian", "", ExpectedException = typeof(InvalidUserException))]
[Row("", "123", ExpectedException = typeof(InvalidUserException))]
[Row("Christian", "qwerty", ExpectedException = typeof(InvalidUserException))] 
public void CheckUserLogins(string name, string password)

Det har desuden også en feature til at rulle database ændringer tilbage ved hjælp af attributterne SqlRestoreInfoAttribute, RollBack og RestoreDatabaseFirst.

XUnit findes på codeplex, og det er udviklet med henblik på at skulle passe bedre ind i .NET miljøet, samtidig med der er ryddet op i attributter og assertions med henblik på at få pænere testkode. Jeg kan personligt virkelig godt lide at det gør brug af generics, og at det giver mulighed for at lave en slags aspect oriented programming.

Generics bruges blandt andet til fixture setup, sådan at man ved at bruge IUseFixture<T> kan passe klasser til ens testmetoder, som man så kan arbejde med.

public class MyTests : IUseFixture<MyFixture>, IDisposable
{    
    public void SetFixture(MyFixture data)    
    {                
        data.SomeMethod();    
    }
}

Aspect oriented programming fungerer ved at man laver ens egne attributter ved f.eks. at extende BeforeAfterTestAttribute, som blot er at overskrive et par metoder.

public class PrepreDatabaseAttribute : BeforeAfterTestAttribute
{    
    public override void Before(MethodInfo methodUnderTest)    
    {        
        // Do your pre-test stuff    
    }    
    public override void After(MethodInfo methodUnderTest)    
    {        
        // Do your post-test stuff    
    }
}

Udover et decideret testframework skal man også bruge et tool til at udføre ens tests og give overblik over hvad der går godt og hvad der fejler – de berømte grønne lamper man nyder at se lyse når man benytter TDD. NUnit har den fordel at der følger et lille separat tool med der kan lige præcis det, og som virker ganske fint til de flestes behov. Til XUnit er der et lignende tool under udvikling, men det er stadig eksperimentalt og virker ikke just som et færdigt tool endnu.

Alle 3 frameworks kan imidlertid også bruges sammen med TestDriven.NET og ReSharper , som begge er bedre bud hvis man vil have en ordentlig brugsoplevelse.

 TestDriven.NET er et add-in til Visual Studio, som giver mulighed for afvikling af unit tests, og er det tool flertallet bruger. Jeg må dog erkende at jeg synes TestDriven.NET er relativt dyrt, taget i betragtning af at det startede som et gratis værktøj og i bund og grund stadig er et simpelt add-in.

Resharper er også et Visual Studio add-in som man kan skaffe til rundt regnet samme pris som TestDriven.NET, men man får bare meget mere med i pakken, da det blandt andet også tilbyder hjælp til refactoring, formattering og kode analyse. Som det fremgår er jeg selv faldet lidt for ReSharper, selvom jeg var lidt skuffet over at det endnu ikke er klar i en .NET 3.5 udgave, så kodeanalysen fejler hårdt på LINQ queries o.lign. En ny version målrettet .NET 3.5 skulle dog være på trapperne, så jeg må bare leve med at slå kodeanalyse fra indtil da.

Test af sprocs – der hvor det bliver mere vanskeligt.

Når jeg nu tidligere har nævnt at jeg mener sprocs er vanskelige at teste, skylder jeg også at beskrive hvordan man kan gøre det - og uddybe hvad jeg mener med at værdien i mine øjne er mindre end ved test af mere isolerede funktioner.

Jeg læste i går en ganske god artikel i MSDN magazine omkring brugen af LINQ to SQL til test af sprocs. Der var egentlig ikke et perspektiv der i første gang havde slået mig, men det er egentlig klart at LINQ to SQL er godt til at teste sprocs på meget lettere vis end det var muligt i tiden pre-LINQ. Helt enkelt består sproc tests af.

·         Klargøring af database, så den er i en velkendt tilstand.

·         Afvikling af sproc.

·         Test af databasens tilstand er som ønsket.

De tre trin lyder simple nok, man har naturligvis en række komplikationer i forhold til det her med databasens tilstand. Det letteste er sandsynligvis at arbejde med en tom kopi af databasen, som gerne er separat fra ens anden test/udviklings database der typisk indeholder en røvfuld ”real world” data. Ved hjælp af LINQ kan man så faktisk med meget få linjer kode slette alle data i en tabel, og indsætte de data man ønsker som grundlag for testen.

Ligeledes kan man i LINQ afvikle den sproc der skal testes, og så er der egentlig ”bare” tilbage at teste databasens tilstand efterfølgende. Et godt tip fra artiklen er at hente alt data i en tabel, hashe det ved hjælp af eksempelvis en MD5 hash. Resultatet kan så meget enkelt sammenlignes med en hash som man udregner ved udformning af testen – noget der kan være en kende besværligt, da det at tage hashen første gang man afvikler koden kun beviser at resultatet er det samme ved efterfølgende afvikling og ikke nødvendigvis om det er korrekt.

Få overblik med Unit testing af sprocs

En anden måde at bruge unit tests på i forbindelse med sprocs, som jeg selv har været med til at implementere er at skrive en parser der undersøger hvilke sprocs man kalder fra sin kode og sammenligne med de sprocs der rent faktisk findes i databasen. Selvom det ikke er nogen test af funktionaliteten som sådan, kan det på store projekter bruges til at teste om de sprocs man kalder findes og omvendt om man har sprocs der ikke længere bliver brugt.

Lidt i samme boldgade kan man skrive en unit test der læser ens sprocs ud, og gemmer dem i databasen igen - giver dette en exception er det en god indikation af at sproc’en er knækket. Igen er det ikke nogen gylden løsning da man ikke finder logiske fejl og ikke engang alle slags fejl på den måde. Det er imidlertid et simpelt værktøj, som har sin berettigelse i at det kan give overblik og finde alvorlige problemer med meget simple midler.

Tags: ,

Comments (3) -

Nis L. Simonsen
Nis L. Simonsen Denmark
6/2/2008 1:17:49 PM #

God artikel med gode pointer.
To kommentarer:
1) Jeg er rimeligt sikker på at udtrykket hedder "the devil is in the details" og ikke "god" Smile
2) Ifht. sproc-tests, hjælper det en del hvis man scripter hele sin database - det synes jeg der er mange gode grunde til at gøre uanset om man så unit-tester den eller ej.

DotNetNerd
DotNetNerd Denmark
6/2/2008 10:27:55 PM #

Hej Nis, tak for din kommentar, jeg er glad ved at høre du synes godt om artiklen. Jeg vil dog godt delvist modargumentere Smile

1) er ikke korrekt, da udtrykket findes i begge udgaver, alt efter om man vil give det med detaljerne et positivt eller negativt spin. Den her bog er et eksempel: www.amazon.com/.../0415925649 og der er mange flere at finde på google Smile

2) jeg er helt enig i at scripting af databasen kan være "the way to go", og at det desuden har en masse fordele.

Min pointe går på at det er mere tidskrævende/besværligt at skrive den slags tests - fremfor små isolerede funktioner uden sideeffekter. Af den årsag kan det være for dyrt i forhold til den gevinst man opnår. Det afgørende her er hvilken type projekt vi snakker om? Værdien af testen kan variere ud fra hvor meget reel funktionalitet man har, og hvor kritisk det er om der opstår en fejl.

Jeg tænker især projekter der bærer præg af at være RAD uden den store pose penge i ryggen, og hvor der er relativt lidt funktionalitet. I de tilgælde kan det være fordelagtigt at anvende nogle simplere metoder til stadig at teste nogen ting, om end på en mere overfladisk måde.

DotNetNerd
DotNetNerd Denmark
6/3/2008 3:51:08 PM #

1000 tak, det er rart at høre at der er nogen der har noget ud af det jeg skriver.

Jeg sætter stor pris på kommentarerne, da det både giver mig mulighed for at lærer noget, men også sikrer at jeg får uddybet mig godt nok. Det kan bliver lidt ensidigt hvis man bare sidder med sine egne tanker og skriver.

Who am I?

My name is Christian Holm Diget, and I work as an independent consultant, in Denmark, where I write code, give advice on architecture and help with training. On the side I get to do a bit of speaking and help with miscellaneous community events.

Some of my primary focus areas are code quality, programming languages and using new technologies to provide value.

Microsoft Certified Professional Developer

Microsoft Most Valuable Professional

Month List

bedava tv izle