torsdag 17 december 2009

"The pit of despair"

Nyligen har vi i vårt team gjort en ansats till att göra mera unit tests. Det är en grym känsla när vi alla verkar få samma "tända glödlampa" över våra huvuden, när insikten om att det här är vad vi borde göra slår oss alla samtidigt. Att vara överrens underlättar ju helt klart.

Men vägen till ett bättre och förändrat arbetssätt är motig. Jag har flera gånger den senaste tiden varit på god väg att ta en liten genväg för att slippa ta den jobbiga vägen över unit tests. Jag vet ju självklart innerst inne att vi inte kan fortsätta koda på utan tester, alla de buggar jag kämpat med är ju ett bevis på att något måste bli bättre. Men ändå kryper den gamla vanan fram ibland och man kommer på sig själv att argumentera för att försvara sitt tillfälliga undantag. "Ja men den HÄR grejen kan jag ju inte testa... den är ju trivial och dessutom svinjobbig att testa!"

Som tur är finns det fler än jag i teamet. Hade det bara varit jag hade min monolog enkelt kunnat låtit logisk och ja själv hade säkert accepterat ursäkten för att tillfälligt undvika från planen. Tillfällena hade nog blivit fler... och ännu fler. Till slut sitter man där igen, inte ett dugg bättre och med samma gamla buggar som välkomnar en på morgonen. Men har man människor runt sig som ifrågasätter undantaget (ja ibland räcker det med en tveksam blick för att fånga samvetet...) så blir det inte så.

Nä, det är bara att bita ihop! Under Dan Norths föreläsning på Öredev pratade han om "the Satir Change Model". Den beskriver ungefär hur vi hanterar förändring och hur vi tar oss vidare. Det är en stegvis process.
Steven Smith beskriver den bra i sin blog.


I stort går det ut på att vi befinner oss i ett nuläge, trygga i det vi kan, det vi är vana vid och vårt beteende, även om det är ett destruktivt beteende. En förändring introduceras och genast föds ett motstånd mot det nya där vi, medvetet eller omedvetet, motarbetar förändringen. Nästa steg är KAOS. I botten av detta kaos finns "the pit of despair". Nu är vi på botten och har svårt att se hur vi ska kunna ro iland förändringen.

Men - låter vi oss komma förbi denna grop väntar integrering och nya insikter, när vi ser hur det nya faktiskt hjälper oss i vårt arbete. Och fullföljer vi integreringen väntar ett nytt nuläge där vi förbättrat oss och vant oss vid nya, mindre destruktiva beteenden.

Tänker jag efter känner jag klart igen mitt eget beteende. Fastän jag var fullt delaktig i initiativet till förändringen har jag gjort motstånd med mina lama ursäkter om att slippa undan. Just nu befinner vårt team sig i gropen och med sprattlande armar försöker vi gräva oss upp. Men att bara inse att det är där vi är hoppas jag är ett bra tecken.

Det är dags att släppa motståndet och låta kaoset styra ett tag.
Snart är vi på väg upp igen. Åtminstone lagom till nästa förändring...

Over and out!

Dagens kodarmusik: Ingen musik, utan lite Hanselminutes med Roy Osherove om Unit Testing.

söndag 13 december 2009

Dags att spela i samma lag!

Min uppfattning om vårt yrke, systemutvecklare, har alltid varit att vi gör mer än att knacka kod. Vi gör mer än att designa mjukvaruarkitekturer. Vi löser behov. Vi utvecklar verksamheten tillsammans med de som arbetar i och med den. Utan dem skulle det inte finnas någon verksamhet, inga behov och inget berättigande för vårt yrkes existens. Tyvärr upplever jag att vi ganska sällan kommer till vår fulla rätt.

Den "färdiga" lösningen på ett fat
Allt för ofta kommer en färdig lösning till oss från användare eller ledning. Det innebär att någon annan än vi har tänkt ut vilket systemstöd som ska finnas och när det måste vara klart. Vi kommer in allt för sent i processen när viktiga beslut redan är fattade som på många sätt inskränker i vår möjlighet att utöva det vi är bäst på - att utveckla IT-stöd. Om vi redan tidigt fanns med i processen kunde vi bättre förstå skäl och bakgrund till det man vill åstadkomma och kanske finna bättre vägar eller ännu bättre lösningar. Men när man får en färdig lösning finns det oftast varken tid eller mandat för att gå tillbaka och riva upp beslut eftersom någon annan ju redan "vet" att det är den enda och bästa vägen. Sen sitter man som utvecklare och knackar ihop något man egentligen inte tror på.

Två månader på IT-kammaren och sen är det klart
Ett annat vanligt scenario är att vi IT-människor får allt för fria händer. Vi tolkar lite tunna uppgifter vi fått för att sedan stänga in oss och designa en lösning som ur vårt perspektiv verkar klockren. Tyvärr slutar det ju ofta med väldigt komplicerade funktioner som användarna inte förstår eller tycker fungerar i deras vardag. De vet inte vad de ville ha, men det som levererades var inte rätt. Lösningen har fått för stort IT-fokus. Fundera en stund över den där dialogen du designade med lite väl "data-nära" termer här och var...

Det här är två ytterligheter, men de pekar båda på samma område där vi allt för ofta brister - samarbete.

Vi måste på ett bättre sätt lyckas använda varandra. De har kunskap om verksamheten och lever i den varje dag. De är beroende av det vi levererar och måste leva med det, varje dag. Vi har kunskap om hur man gör bra IT-stöd, hur man kan samverka med befintliga lösningar och vilka tekniker som är aktuella idag. Var för sig kommer vi till korta, men tillsammans har vi ju en rätt bra laguppställning. Vi täcker ju in alla väsentliga områden! Vore det då inte bättre om vi blev lite mindre DE och lite mera VI?

VI jobbar gemensamt eftersom vi har ett gemensamt mål, en verksamhet som fungerar bra och presterar på topp. Genom att utnyttja våra olika kompetenser och roller på bästa sätt kan vi få mycket bättre effekt och undvika feltolkningar. VI är större än meningslösa motstridigheter, vi bryr oss om vårt gemensamma arbete och vårt gemensamma resultat. Det spelar ingen roll vem eller vad som är skälet till att något inte fungerar eller brister, vi har ett gemensamt ansvar för att få det att fungera.

Det här är några punkter jag tänker arbeta mer för framöver:
  • I varje verksamhetsprojekt få med kunskap om IT och möjliga lösningar tidigt
  • Upprätta en bra dialog tidigt, lyssna - vad är behovet egentligen?
  • Leverera små, körbara, delar av vår lösning regelbundet som vi kan prova gemensamt - på riktigt!
  • Aldrig mer säga "ok, då fattar vi" och försvinna in på kammaren och tok-koda i 3 månader
  • Aldrig mer säga "ja den lösningen blev ju sådär, men det var ju de på X fel, så det är inte mitt problem"
Vi är ett och samma lag. Vi har ett och samma mål.
Så vad är problemet?

Over and out!

Dagens kodarmusik: Europe - New love in town

måndag 7 december 2009

Unit testing success story!

Jag var bara tvungen att skriva en liten glädjepost om mitt första riktiga äventyr med unit testing.
Jag har under de senaste dagarna lagt till några nya funktioner i en grafisk kontroll. Den består i stort av en datagridview med ett antal rader och kolumner. Vissa rader är summor av ovanstående rader, vissa kolumner är summa av radens värden. Alltså, det finns lite relationer raderna och kolumnerna emellan.
I samband med att jag gjorde de nya funktionerna skrev jag i princip om hela kontrollen och dess affärslogik för att göra den mera testbar. Det verkade som en liten lagom prototyp. Och det var det.

För att bara beskriva läget före min unit testing refactoring, såg det ut ungefär så här:

  • Inga unit tests
  • Grafisk kontroll: Specialhantering för alla typer av rader och kolumner, kontroll mot konstanter och vissa magic numbers. Mycket kontroll av inmatade värden och omvandling från string till int.
  • Coordinator: Specialhantering av samma konstanter, 150 rader kod. 
  • Affärsobjekt: Ärvda versioner av riktiga affärsobjekt för att kunna blanda affärsobjekt med rena summeringsrader och kolumner. Konstanter och magiska siffror lite här och där.
  • Acceptanstest: Starta applikationen. Prova funktion 1, funkade inte. Ändra lite. Prova igen. Prova funktion 2, funkar! Prova den tredje funktionen, funkade inte. Ändra. Funkar! Prova lite till. Nu funkar inte funktion 1. Osv osv. Nervöst inför releasen... Buggfix redan första dagen.

Och så efter...

  • 40 unit tests, all green!
  • Grafisk kontroll: Bara hantering av hur cellerna ska se ut, hur de formatteras mm. Hämta värde och skriva värde till och från affärsobjekten sker med en enda enkel metod. 
  • Coordinator: En konstruktor och två properties. Det är allt.
  • Affärsobjekt: GUI wrappers som ibland håller referenser till affärsobjekt, ibland inte. Hel uppsatta för att på bästa sätt stödja GUI programmeringen. Och helt testbara!
  • Acceptanstest: Allt funkar med en gång! Iallafall ser det ut så...
  • Känns grymt bra inför releasen!
Med andra ord, att skriva unit tests för min lilla funktionsändring gjorde inte bara att jag fick mer förtroende att den fungerar. Det gjorde också lösningen betydligt bättre eftersom en testbar arkitektur helt enkelt är en bra arkitektur. Testbarhet motverkar nämligen "fix-och-trix-lösningar" som jag lätt faller tillbaka till. Tyngdpunkten på funktionerna ligger i affärslogiken. GUI:t hanterar bara grafisk representation.

Sen kan man säga vad man vill om att jag kanske även utan unit tests borde sett att lösningen... ja...sög.
Men det är skit samma nu. Med hjälp av en unit testing utflykt och genom att tänka på testbarheten blev det en självklarhet även för mig.

Att inte skriva unit tests i fortsättningen känns otänkbart. Nu är de här för att stanna, för den känsla jag nu har inför det jag presterat och inför en stundande release är något jag vill vänja mig vid.


Ett litet citat på vägen, som inte alls har med ämnet att göra, men som var så bra. Andy Hunt sa i sitt keynote på Öredev 2007 på temat "Accidental Complexity", dvs saker som är komplext utan att det egentligen finns skäl till det:
XML is like kids. They start of small and cute. Then they grow.

Over and out!

Dagens kodarmusik: Riva - Time is the healer (Armin van Buuren remix)

söndag 6 december 2009

En nybörjares syn på unit testing

EN sådär 10 år efter branschen är jag igång med lite unit testing. I den nya funktion jag nu ska implementera har jag försökt göra så kompletta unit tests som möjligt. Det är inte lätt.
Så här långt in i kampen har jag kommit fram till ett par saker:

Använd mocks - på rätt sätt
En Mock är ett fake-objekt, som man använder istället för den verkliga implementationen. Man ersätter då alla anrop på en verklig klass med anrop på en mock. Du kan då i testet styra vad din mock ska svara på anrop och på det viset skapa en känd omvärld för ditt test. Generellt sett har jag haft mest användning av mock objekt när det gäller de olika skiktens ställföreträdare och inte så mycket när det gäller affärsobjekt. Till exempel är det lättare att göra en fake implementation av en "ProduktManager", som innehåller funktioner för att hämta och spara produkter än för själva klassen "Produkt" i sig.

Men här märker man direkt om arkitekturen är testvänlig eller inte (läs: bra eller inte...). Om det inte är möjligt att byta ut sina "managers" mot andra implementationer är det svårt att använda mocks. Men om alla användare av managern använder ett interface är det enklare. Om man dessutom använder en instance factory eller en IOC containter (här är jag dock på tunn is) gör det hela lösningen ännu mer testbar.

Sen försöker jag dra gränsen för både mocks och omfattningen av test vid ett skikt. Det innebär att man testar ett skikt i taget och det enda som man "mockar" är skiktet under det skikt man just nu testar. (Phew, hur blev det där egentligen?).




Så, för att förklara:
Om jag ska skriva ett test för en coordinator, kommer jag att ge den en eller flera mocks för de managers den använder. På det sättet ger jag den klass jag använder givna förutsättningar. Inget konstigt så långt. Där drar jag gränsen för testet. Repository skikten är aldrig inblandat i testet. Inte heller GUI. Det gör testet mindre, mer fokuserat och enklare att skriva.

Behovet av att använda mocks för andra objekt, som entiteter har jag ännu inte riktigt utrett. Det jag har märkt är att det kan hjälp att skapa enkla metoder för att fylla på de riktiga affärsobjekten med testdata. Men jag använder då i vilket fall det riktiga affärsobjektet. Men vem vet, kanske din hjälpfunktion är en indikation på att du behöver den även i produktionskoden?

Ett sätt att bemästra GUI tester... lite
Hur man testar GUI är ett problem för mig. Men jag har hittat en utväg. Gör GUI:t dummare.

Med en coordinator (som i vårt fall är den GUI pratar med) som levererar så färdig information som möjligt kan man lägga mer kod i den mycket mer testbara coordinatorn. Att tänka på vad som är enklast att hantera för ett GUI, och bygga coordinatorn efter det, gör skillnad.

Funktioner som exempelvis tar in strängvärden kan man evaluera om värdet var giltig integer eller ej i coordinatorn. Sen får GUI:t hantera det som den vill, men problemet kommer inte sprida sig vidare eftersom vi med tester i coordinatorn sett till att alla felaktiga värden hanteras korrekt.

Ett annat bra sätt är att helt ta bort null-problematiken för GUI:t. Låt aldrig coordinatorn returnera null, utan istället en tom lista, tom sträng etc. Kanske är det till och med så att vi borde skapa VårKlass.Empty för att alltid ge tillbaka något. Hur många NullReference problem har man inte fått...

Vid databindning i en DataGridView, använd antingen CellValueNeeded och CellValuePushed som pekar på GetValue(...) i coordinatorn och SetValue(...) i coordinatorn. Eller, vilket oftast är ännu bättre, skapa tillrättalagda, testbara GUI objekt com är gjorda för databindning.

Kontentan är att GUI:t ska vara så dumt som möjligt! Då slipper vi komma på obskyra sätt att testa det med unit tests. Då kan GUI:t testas manuellt utifrån användbarhet och inte för att hitta buggar med vad jag ibland kallar för "Happy testing". Klicka här, klicka där... klicka lite här.... Ja du vet vad jag pratar om.

Håll testerna rena och tydliga
När TDD förespråkarna gav oss utvecklare fribrev vad gäller dokumentation hurrade nog inte bara jag. "Jag skriver kod istället för dokumentation - grymt!". Men då gäller det ju att testerna går att läsa som dokumentation. Här har jag hittat ett par bra tips
  • Låt testerna få tydliga namn, hur långa som helst bara de är tydliga
  • Uttryck testerna som ett krav eller påstående, som SökningPåOkändProduktGerTomLista eller IckeNumerisktVärdeFörAntalPåverkarInteDataOchKastarInteException.
  • Undvik onödiga Asserts i testet. Om du har gjort en assert i ett testfall, gör inte om den i ett annat. Till exempel, du har en funktion som alltid ska returnera resultat och inte null. Säkerställ detta i testet HämtaProdukterReturnerarAlltidEttVärde. I alla andra tester förutsätter man sedan att så är fallet. Inga if != null, inga Assert.IsNotNull, såvida det inte gäller något specifikt för just det nya testet.
  • Skapa hjälpfunktioner i din klass som gör komplexa eller otydliga operationer.
    Jag råkade ut föra att jag i många tester behövde hämta ut ett objekt från en IEnumerable med hjälp av LINQ. Koden blev rätt hårig och var egentligen inte del av testet. Istället för att skapa massa komplex kod i mitt test la jag ut den koden i en hjälpfunktion med enkelt namn. Då ser testet snyggare ut och blir enklare att förstå. Dessutom är det ju inte fel att lägga ett test på hjälpfunktionen också, en gång för alla så att inte heller resultat från den behöver ifrågasättas.
  • Använd alltid möjligheten till att skriva en kommentar till din Assert, alltså en sista inparametern till Assert, inte som en kommentar i koden.
  • Skriv aldrig kommentarer i testkoden. Den ska inte behöva kommenteras.
  • Ta utan att blinka bort tester som heter TestMethod1 eller TestaProdukt mm. Vad gör du när ett sånt test fallerar? Nä, dåligt namngivna tester är faktiskt sämre än inga tester, iallafall ur dokumentationssynpunkt.
  • Låt dina Asserts återspegla vad testet heter, dvs det ska var tydligt att din Assert testar just det som namnet på ditt test säger
  • Klappa dig själv på axeln om du gör ett test med bara EN assert i. Bra gjort!
Med andra ord - ställ samma krav på testkoden, eller högre(!), som på produktionskod vad gäller tydlighet och format.

TDD eller... ?
Precis som med Unit tests är det ju inte direkt ett buzzword längre. TDD ha funnits ett bra tag, men jag har inte lyckats ta det till mig än. Jag tycker Uncle Bob är en riktig hjälte med många bra idéer. Jag skulle gärna vilja känna att TDD är min grej. Du vet väl vad TDD reglerna är?
  • You are not allowed to write any production code unless it is to make a failing unit test pass.
  • You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  • You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
Mitt förfarande för unit tests är just nu trevande och utforskande. Det består i stort i att koda i mindre sjok för att sedan skriva tester i mindre sjok och så börja om igen. Istället för att känna mig missnöjd över att jag ännu inte kör TDD väljer att se det så här: Nu gör jag tester, det gjorde jag inte förut. Mina kodsvängar är kortare nu och jag tänker hela tiden på testbarheten. Jag är på gång!

Uncle Bob själv sa sig vara lite skeptisk till TDD först, men blev helt såld på det. Jag gillar ett citat han gav angående insikterna efter att ha givit sig hän till TDD, det säger lite av min känsla också.
I can no longer concieve of typing in a big long batch of code hoping it works. I can no longer tolerate ripping a set of modules apart, hoping to reassemble them and get them all working by next Friday.
- Uncle Bob

Over and out!

Dagens  kodarmusik: Supersnälla Silversara - Samla Samla (okej udda, men den är OMÖJLIG att få ur huvudet!!! Prova - jag vill inte lida själv.)


Lite länktips
Know Your Units - Kevlin Henney, Curbralan, UK
Coplien och Martin debaterar TDD, CDD och professionalism
TDD artikel från mästaren själv, Uncle Bob