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: , , ,

Annons

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: , , , ,


Varför enhetstesta – anledning 1

13 december 2008

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

Anledning 1 – Dokumentationseffekten

Anledning 2 – Säkerhetsbälteseffekten

Anledning 3 – Designeffekten


Jag tänkte köra en serie inlägg med fokus på anledningar till att enhetstesta. Jag kommer att köra dem utan inbördes ordning – alltså utan någon sorts prioritering dem emellan. Delvis för att jag inte är 100% säker på vilket argument som är det viktigaste. Det finns, som jag upplever det, många anledningar! Gissar på ett inlägg i veckan, vi får se hur snabbt det går.

Så här kommer en anledningen idag; det blir den dokumenterande effekt enhetstester har.

Om man jobbar på ett projekt tillsammans med några kollegor, eller för den delen tittar på sin egen kod från några månader sedan, händer det att man tar sig för pannan och undrar ”Hur fungerar nu den här klassen/funktionen igen?”. Exempelvis kan jag ställa mig frågor i stil med dessa:

nintendo-lego-video-game-01

  • ”Kan jag skicka in null för parametern secondaryPoly eller klarar inte funktionen av det? Måste jag skapa en tom poly då i min situation?”
  • ”Är det meningen att jag ska lägga till alla A-punkter först och sedan alla B-punkter i gridden? Kan jag blanda?”
  • ”Kan jag använda Compute() och sedan lägga till några A:n, för att sedan kunna använda Compute() igen?”
  • ”Kommer metoden att krascha om jag skickar in ett negativt tal, eller bara returnera 0?”

Den typen av frågor – om hur det är tänkt att en funktion eller ett objekt ska fungera – tillhör egentligen dess dokumentation. Men som van programmerare vet jag väldigt väl hur sällan dokumentation kommer på plats – och hur lite jag litar på dokumentation som finns (den blir så snabbt gammal pga ändringar i kodbasen!). Ofta är inte heller dokumentationen heltäckande (så kallade edge cases beskrivs inte, utan bara huvudfunktionen).

Utan enhetstester, finns det två vägar – störa sin kollega (om det inte är min egen kod!) – eller läsa igenom hur koden fungerar genom att titta under huven – läsa källkoden direkt. Och om man frågar en kollega är det inte alltid man får 100% pålitliga svar – de har inte allt i huvudet, lika lite som jag.

Med enhetstester finns det en tredje väg. Du kan kolla direkt på hur koden är tänkt att fungera genom att läsa igenom enhetstesterna!

För att få lite smak för detta argument, jämför hur du förstår klassen ”Countdown” genom att både läsa dess kod och dess enhetstester. Observera att detta är en väldigt liten klass – och med en större klass blir den dokumenterande effekten än viktigare såklart!

Countdown.cs (klasskod)

CountdownFixture.cs (enhetstester)

Taggar: , , , ,


Google Mock

12 december 2008

images4Har just fått nys om ett nytt mocking framework för C++, utvecklat av Google. Det är BSD license så det är helt öppet. Läs mer här.

Not: Mocking är att skapa ”fakeinstanser” av objekt i objektorienterade språk – det underlättar vid enhetstester där flera objekt är inblandade.  En mock påminner lite om en papegoja på så sätt att den upprepar vad du sagt till den! Du kan läsa vad jag skrivit om mocking här.

Taggar: , , , ,


Fattigmannens enhetstestning

08 december 2008

imagesJag är vän av enhetstestning. Speciellt den variant som innebär att man skriver tester före kod. Den tekniken är känd som TDD – Test Driven Development. Du kan läsa alla mina postningar om TDD genom att klicka här.

Men i denna postning tänkte jag introducera en enkel idé för att komma igång med enhetstestning, utan att behöva några speciella verktyg. Det finns nämligen en hel uppsjö med enhetsverktyg för de populära språken – ofta flera konkurrerande verktyg till varje språk! För att slippa bry sig/lära sig/installera och sätta sig in i något enskilt enhetstestverktyg tänkte jag berätta hur jag gör när jag stöter på någon ”främmande miljö” jag behöver skriva enhetstester i, där jag inte orkar sätta mig in i ytterligare ett enhetstestningsverktyg.

Idén är att ASSERT() är det enda vi behöver för att göra enhetstester!

Och assert() finns i så gått som alla miljöer man stöter på – kanske med namn som assume eller något liknande. Och om det inte finns, kan man lätt skriva en egen assert-metod. T.ex. mha exceptions- exit() eller halt-instruktioner, beroende på vilket programmeringsspråk/scriptspråk man nu befinner sig i.

Jag håller mig till programmeringsspråket C i denna postning, då jag tror att det är det mest spridda språket och därför är lätt för många att ”översätta” till sitt favoritspråk.

Låt säga att vi vill utveckla en metod som kontrollerar om en textsträng innehåller en annan textsträng. Vi kallar metoden ”Contains()” och den har följande signatur:

int Contains(char* source, char* substring);

Vi vill alltså att Contains(”abc”, ”ab”) ska bli ”-1” medan Contains(”abc”, ”g”) ska bli ”0”.

Här är skelettet till ett enkelt C-program:

#include <assert.h>

int main() {
  MainLoop();
}

int MainLoop() {
}

Jag har lagt till ”include <assert.h>” för att få med assert()-funktionen i programmet. Nu lägger vi till en metod ”RunSelfTests()” som är till för att köra de automatiska enhetstesterna när programmet körs:

#include <assert.h>

int main() {
  RunSelfTests();
  // Alla självtester gick igenom, kör huvudprogrammet!
  MainLoop();
}

int MainLoop() {
}

int RunSelfTests() {
}
// Kompileringsrad:
// gcc -o test1 test1.c

Ovanstående kodskelett är allt vi behöver för att komma igång med enhetstestning i C!

Nu kommer vi till själva kärnan i detta inlägg: enhetstesterna.

Ett enhetstest testar en ”punkt” i beteendet hos en ”enhet”/”aspekt” av programvaran. Rent konkret kontrollerar man att resultatet (utdata) av viss speciell indata är korrekt.

Man väljer gärna enkla fall, så att enhetstestet blir läsbart, lätt att skriva och förstå.

I vårat fall listar vi helt enkelt upp några specialfall och det förväntade resultatet av våran funktion Contains():

  • Contains(”abc”, ”abc”) ska vara sant
  • Contains(”abc”, ”def”) ska vara falskt
  • Contains(”olof”, ”of”) ska vara sant

.. och så vidare tills vi känner att vi kan ”lita” på våran funktion Contains() till 90% (100% är dumt att sikta på! Buggar finns alltid kvar i alla program, det lär man sig som programmerare med åren..)

#include <assert.h>

int Contains(char* source, char* substring) { return 0; }

int main() {
  // Kör själv-tester (asserts) först av allt.
  RunSelfTests();

  // Alla självtester gick igenom, kör huvudprogrammet!
  MainLoop();
}

int MainLoop() {
  // ej implementerad ännu..
}

int RunSelfTests() {
  assert(Contains("abc", "abc"));
  assert(Contains("abc", "bc"));
  assert(Contains("olof", "of"));
  assert(Contains("abc", ""));
  assert(!Contains("bc", "abc"));
}

Vi kan testa att kompilera & köra vårat program med ”gcc -o test1 test1.c” eller om du kör Visual Studio i Windows någon F-knapp. När vi kör programmet får vi ett felmeddelandet i stil med ”Assertion failed ‘Contains(”abc”, ”abc”) at row XX”.

Vi implementerar nu ”Contains” med hjälp av string.h-funktionen ”strstr” som fungerar ungefär som Contains faktiskt, men inte riktigt:

#include <assert.h>
#include <string.h>

int Contains(char* source, char* substring) {
  char* pos = strstr(source, substring);
  if(pos == 0)
    return 0;
  else
    return -1;
}

int main() {
  // Kör själv-tester (asserts) först av allt.
  RunSelfTests();
  // Alla självtester gick igenom, kör huvudprogrammet!
  MainLoop();
}

int MainLoop() {
 // ej implementerad ännu..
}

int RunSelfTests() {
  assert(Contains("abc", "abc"));
  assert(Contains("abc", "bc"));
  assert(Contains("olof", "of"));
  assert(Contains("abc", ""));
  assert(!Contains("bc", "abc"));
}

Nu ska våran kod kompilera och köra utan att någon assert() kraschar!

Taggar: , ,


%d bloggare gillar detta: