Testing filters

07 april 2009

muir-woods-mossy-fernsSo every once in a while I stumble upon an instance of this problem:

  1. I have a list of entities of type A
  2. I want to filter out some of the entities, with a predicate P returning a boolean for each A-instance a in the list

I try to test the predicate by building some ”positives” and some ”negatives”. But often I get the nagging feeling either the positives or the negatives form such a huge space of possibilities, that it becomes a pain to create instances of even a small subset of the variations.

For example, if I have some base-type/interface Base and the elements of the list are subtypes of this type, S1, S2, …, SN and the filter is supposed to let through only elements of type S1 and S2 but not Sk, k>=3. Also instances of S1 and S2 have to have some certain properties fullfilling some criteria to pass. You can imagine the combinatorial explosion of instances I have to try out in the test code, even to test a subset of all possibilities.

Add to that that some of the Si subtypes have 2 or 3 constructor parameters, and we’re in the jungle.

My current approach is

”test one to three positives, and one to three negatives. Ignore the nagging feeling”.

How do you approach this example?


Taggar: , , ,

Annonser

Enhetstester & simulering: en analogi

05 mars 2009

På senare tid har det blivit alltmer populärt med så kallad virtualisering. Det innebär att man via ett program kan köra andra operativsystem än det man har installerat på datorn. Detta sker genom så kallad mjukvaruemulering.

Det kanske låter som magi, men det är egentligen – i princip – väldigt enkelt. Ett operativsystem som kör på en dator är nämligen inget annat än en massa instruktioner (heltal) i en lång följd, och om man kan följa vad dessa instruktioner betyder, till punkt och pricka, kan man ”lura” operativsystemet att det körs på en äkta, fysisk dator. Men i verkligheten körs (”emuleras”) operativsystemet ”inuti” ett vanligt program!

Ett par populära sådana virtualiseringsprogram, för att simulera PC är VirtualBox och VirtualPC.

Ubuntu + WinXP

Ubuntu + WinXP

Till exempel har jag Ubuntu installerat på min dator hemma. Jag har installerat programmet VirtualBox i Ubuntu, och inuti detta program har jag installerat WindowsXP! På så sätt kan jag köra bägge operativsystemen samtidigt!

Nåja, detta är inte en bloggpost om virtualiseringsteknik, eller emulering som är en vanligare benämning vid simulering av lite äldre datorsystem som wonderboy2Commodore 64 (I love you!) eller TV-spel som Sega-16-bit (love you too!).

Låt mig bara dra en parallell till innan jag kommer till inläggets kärna!

Inom fysiken jobbar man med matematiska modeller av verkligheten. ”Matematisk” betyder bara att modellen består av ekvationer. Modellen är en förenkling av verkligheten, men man strävar såklart efter att den är tillräckligt ‘verklig’ för att, när man via numerisk analys (fint uttryck för simulering!) genomför en så kallad integration eller beräkning av modellen, kunna förutsäga hur fenomenet beter sig i verkligheten.

Numerisk analys

Numerisk analys

Till exempel simulering av solsystemet. Eller luftens passage förbi flygplansvingar, och hur det påverkar vingarna i kombination med hänsyn tagen till gravitation osv.

Båda dessa exempel, virtualisering (eller emulering) och fysik, strävar såklart efter att kunna förutsäga beteendet hos det subjekt man är intresserad av: WindowsXP, Commodore64, flygplansvingar eller solsystemet. 100% träffsäkehet är idealet.

Olika praktiska begränsningar medför att det kan vara svårt att nå de där 100%:en. Inom fysiken är det flera problem man har att brottas med: svårt att mäta indata till systemet, numeriska lösningsmetoder är i sin natur inte exakta (tal i datorer är begränsade i sin nogrannhet), okända fysiklagar (vi har inte en ”teori för allting” ännu) och inte minst: att det krävs oerhörd beräkningskraft för att genomföra beräkningarna (vi har inte tillgång till oändlig beräkningskraft, varken i tid eller minne).

I emuleringsfallet kommer istället svårigheten ifrån att subjektet ifråga, en PC, är komplicerad som attan. Det är svårt att få med allt i simuleringsprogramvaran: en modern PC är en av de mest komplicerade maskiner människan har skapat.

Nu kommer vi äntligen till den ryska dockans minsting! Min observation är följande:ryskdocka

Enhetstestning av programvara fungerar i mångt och mycket som en simulering av (delar av) programmet som utvecklas!


Även i detta fall strävar vi efter 100% prediktionskorrekthet (att kunna specificera programvarans beteende fullständigt), men faller här på praktiska ting som att det är svårt (dvs. dyrt) att enhetstesta vissa delar av program: GUI, tredjepartskod etc. Även den kombinatoriska explosion av användningsmöjligheter mjukvarusystem tyvärr ofta uppvisar, gör det praktiskt taget omöjligt att specificera samtliga vägar ett program kan gå.

Läs gärna mina inlägg om enhetstestning och test-driven utveckling, en moden vidareutveckling av enhetstestning.

Taggar: , , , , , , ,


Endagskurs i TDD/C# någon..?

31 december 2008

images4Har via mailinglistor jag är med på fått reda på att det går en webbaserad endagskurs i TDD för C#-utvecklare. Det verkar vara grunderna och lite till: använda enhetstestverktyg, att skriva tester, refactoring, dependency injection.

Låter det intressant? Besök denna adress och skriv upp dig ..

Taggar: , , ,


Varför enhetstesta – anledning 3

26 december 2008

images7Inlägg i denna serie, ”Anledningar att enhetstesta”:

Anledning 1 – Dokumentationseffekten

Anledning 2 – Säkerhetsbälteseffekten

Anledning 3 – Designeffekten


Vi är framme vid anledning 3: renare kod Designeffekten.

Det kan vara svårt att föreställa sig hur stor skillnad det gör på arkitekturen hos ett mjukvarusystem att börja enhetstesta den, eller till och med skriva enhetstester ända ifrån början.

Den huvudsakliga anledningen till att det gör en sådan skillnad är följande:

Det är svårt att testa ”fulkod”.

Tänk på det en stund. Tänk på vad du anser att fulkod är.

Med fulkod menar jag globalt tillstånd (statiska variabler), beroendegalenskap t.ex. ”spindelklasser” (även kända som ”control freaks”) som känner och kontrollerar tjugo andra klasser (rotar kanske t.o.m. i deras interna data, gud förbjude..), kod i användargränssnittsklasser (logik i event handlers t.ex.), kod som är för nära knutet till databas- eller filsystemslagret av systemet, kod som gör logiska beräkningar nära eller i utritningslogik för grafik etc.

Du har säkert en känsla av vad som är ”fulkod” om du programmerat mer än på nybörjarnivå.

Att det är svårt att testa sådan kod kommer du att lära dig om du börjar enhetstesta ett befintligt system som är lite skräpigt. Jag kan avslöja att en anledning är att koden är så beroende av tredjeparts-API:er vilket gör att testkod måste interagera ganska intimt med biblioteket ifråga – något som tyvärr ofta är om inte omöjligt så åtminstone rejält besvärligt.

När du använder dig av Test-Driven Development (TDD) vänder du på steken: du skriver tester innan du skriver kod! Givetvis blir klasser som designas efter denna princip (tester först!) mycket lättare att testa. Då måste du skriva koden mer eller mindre fritt från inblandning av beroenden, eftersom beroenden är så svårtestade. Givetvis måste din kod fortfarande interagera med tredjeparts-APIer men du kommer att behöva skikta systemet och abstrahera bort detaljer som exakt vilket tredjeparts-API ditt program använder sig av.

Men det som är intressant, och samtidigt svårt att förstå, är att testbar kod per automagi blir renare, snyggare och mer ”pattern-inriktad”/”objektorienterad”/”pick-your-fancy-word”-aktig kod! Pröva själv med ett hemmaprojekt. Så får du se!

Tidigare avsnitt i denna serie:

Anledning 1 – Dokumentationseffekten

Anledning 2 – Säkerhetsbälteseffekten

Taggar: , , , , ,


Varför enhetstesta – anledning 2

20 december 2008

images7Inlägg i denna serie, ”Anledningar att enhetstesta”:

Anledning 1 – Dokumentationseffekten

Anledning 2 – Säkerhetsbälteseffekten

Anledning 3 – Designeffekten


Förra veckan skrev jag ”Varför enhetstesta – anledning 1”. Då tog jag upp den dokumenterande effekt enhetstestning har.

Denna vecka tänkte jag diskutera det säkerhetsbältesliknande system man får när man har enhetstester på sina klasser.images2

Har du varit med om följande? En av supportkillarna kommer fram till dig och säger

Det finns en bugg i kommando X. Om man öppnar den här filen, och kör kommandot på det här och det här sättet, kraschar den här dialogen när man klickar på denna knapp”.

Du är fullt uppe i implementationen av en helt annan del av systemet, men eftersom det är en viktig kund och kunden såklart vill ha det här fixat på stört så är det läge att byta uppgift på momangen.

Buggletandet börjar. Du börjar med att reproducera felet, och mycket riktigt kraschar dialogen när du trycker på knappen i given situation. Du börjar skumma dialogkod och rotar dig ned genom klasser och algoritmer med hjälp av debuggern.

Felet ligger i metod M av klass K som kraschar eftersom den försöker anropa en metod på ett objekt som av någon anledning är Null – Null exception krasch boom bang.

Så du ska in och göra ingrepp i en metod du skrev för ett halvår sedan. Du är inte helt på det klara med hur metoden fungerar – inte heller på de klasser som anropar denna klass.

Vi ska nu göra en studie av vad som utspelar sig i en sådan här situation med och utan enhetstest.

Fall 1: Inga enhetstester

images3 Du är inne i ”gammal kod” (legacy code är kod utan enhetstester enligt Michael Feathers – bibel i min hylla – klicka på bilden!) och vill helst städa upp för att förstå vad som egentligen händer (varför objektet är Null i denna metod); metoden som kraschar är ganska lång (>20 rader kod) och några variabelnamn är förvirrande vilket inte underlättar förståelsen. Och av tidigare erfarenhet vet du hur farligt det är att börja ändra om kod som ”nästan fungerar”. Kommandot funkar ju trots allt i de flesta situationer! Det är ju i just den här konstiga situationen buggen uppstår.

Vad göra? Ja helst vill du röra vid så lite kod som möjligt här i ”djungeln”. Du debuggar länge länge för att förstå vem som anropar vem, vilka relationer objekten har till varandra osv. Till sist fattar du att objektet som ger null-exception inte sätts i en klass K3, givet ovanstående situation, och det felet propageras ned ända till K1 och där blir det Null exception. Du lägger på en ytterligare if-sats i K3 för bryta exekveringen av aktuell metod i den situation kunden har hittat, eftersom det är på tok för mycket förändringar av kodstrukturen (och alla de risker för trasering av andra delar av kommandot det innebär!) för att motivera en korrekt buggfix. Nu undviks åtminstone programkraschen.

Istället hittar du ett alternativt sätt att utföra det kunden vill ha gjort, meddelar support hur och ber honom förklara för kunden. Buggen är så att säga ”fixad” – eller egentligen är den inte det. Det är bara för jobbigt och riskabelt att göra aktuell förenkling/generalisering av koden för att lösa det egentliga problemet. Support förstår och accepterar detta – även om han tycker att det varit bättre om ”det bara funkat”.

Fall 2: Du har enhetstester

Denna gång tittar du på kod som har säkerhetsbälte runt omkring sig.

images4Det betyder att om du förändrar koden, t.ex. för att minska längden på en metod, döpa om variabler och metoder hit och dit, stuva om saker och ting så det går att förstå vad som händer, så kan du köra enhetstesterna för att se om du ”pajat” något. För då skulle något eller flera enhetstester ge ”larm”. Klicka på bilden för att se en lista på enhetstestningsverktyg i massa olika programmeringsspråk.

Känner du skillnaden? Nu kan du strukturera om koden så att det går att förstå vad som händer. Ta bort den där konstiga statiska (globala!) variabeln och instansiera klassen på riktigt från dess anropare, helt enkelt därför att det förenklar koden. Visst behöver du kanske ändra ett eller annat test på vägen (en oftast tämligen enkel process då testkod är och bör vara enkel att läsa!) men du har en distinkt annan känsla i arbetsflödet – du känner dig långt mer säker i dina förändringar. När du förändrar saker behöver du knappt analysera på något djupare plan vad förändringen har för effekter, eftersom enhetstesterna kommer säga till om du förstör något!

Men jag ska inte överdriva hur bra detta system fungerar. Allt beror faktiskt på hur bra enhetstesterna är skrivna – om de inte täcker upp hela klasser så är det vanskligt att ändra i någon metod som inte är har enhetstest. Och även metoder som är täckta av enhetstest kan ha udda kodvägar genom logiken som inte testas av något testfall.

Det är egentligen som med säkerhetsbälte och bilarmed säkerhetsbälte på är du inte garanterad att överleva vid en olycka. Men du har bra mycket bättre odds än utan bälte!

images5

Taggar: , , , ,


Kontinuerlig testkörning i Visual Studio

16 december 2008

Häromdagen skrev jag om kontinuerlig testkörning i Python.

Det experimentet fungerade så bra att jag nu utvecklat en liknande lösning i Visual Studio, där jag kodar i C#.

Samma princip, bara det att jag trycker F6 (build solution, som även sparar icke-sparade filer) istället för Ctrl+S som jag gjorde i Python.

Så här ser det ut: Visual Studio autotesting experiment

  • Till vänster Visual Studio.
  • Uppe till höger ett litet dosfönster som kontinuerligt kör NUnit på aktuellt projekt (hårdkodat, jag ändrar bara i python-scriptet som körs när jag byter aktuellt projet).
  • Nere till höger använder jag OpenOffice som ett klotterplank för refactoring/förenklingsidéer och TODO-lista.

När man stängt av bygget av alla projekt som inte är under utveckling just nu mha Configuration manager går bygget hyffsat snabbt, och eftersom enhetstesterna körs i bakgrunden får jag ett nästan lika snabbt utvecklingstempo som i Python. IntelliSense ger ju en positiv inverkan på hastigheten också, men det känns ändå inte lika kvickt som Pythonmiljön.

Taggar: , , ,


Kontinuerlig testkörning i Python

15 december 2008

Satt och lekte med ett ”autotestscript” i helgen. Det är mycket simpelt uppbyggt:

  1. Kör Test.py som innehåller enhetstest för projektet
  2. Vänta i fem sekunder
  3. Upprepa från 1

Jag har ”snott” idén från en kommentar angående utvecklingen av Google Chrome – tydligen kör Google ibland enhetstesterna kontinuerligt i bakgrunden. På så sätt får man ett ”larm” när man bryter mot något enhetstest!

Rent konkret kör jag scriptet, som jag kallar autotests.py, i ett terminalfönster. Invid detta fönster har jag igång två editorfönster jag skriver kod i, dels enhetstesterna, dels projektkoden. Jag har alltså tre filer i projektet som utvecklas:

  • Main.py – själva programkoden
  • Tests.py – enhetstester för Main.py
  • autotest.py – kontinuerlig körning av Tests.py

Känslan hittills är go! Det blir ett mjukt men ändå högt tempo på kodandet. Lägga till ett test, se det larma, lägga till kod för att få testet att gå igenom. Detta utan att behöva kompilera, vänta på bygget + enhetstestkörning. Bara trycka Ctrl+S när jag skrivit nått nytt.

autotest.py ser ut så här:

import os, time
while True:
  for i in range(0,50):
    print ""
  os.system("python Tests.py")
  time.sleep(5)
autotest

Autotest in action

Taggar: , , , ,


%d bloggare gillar detta: