Testdriven utveckling, TDD, är en viktig teknik i vårt verktygslåda. Alla programmerare behöver behärska den. Hur lär man sig att göra det?
Det finns många som missförstår TDD, kanske för att det ingår test i namnet. Titeln på Steve Freemans och Nat Pryces läsvärda bok “Growing Object-Oriented Software Guided by Tests” kanske bättre beskriver vad det handlar om - att stegvis “odla” kod med tester som vårt verktyg, vår guide.
När man lär ut TDD, vilket jag gjort ofta, och gärna gör igen, möts man ibland av en del motstånd som ofta uttrycks i formen av frågor av typen
- Hur kan man skriva en test om man inte vet vad koden ska göra?
- Varför ska vi skriva dubbelt så mycket kod bara för att…
- Vi vet ju hur koden ska bli, varför kan jag inte bara skriva den?
Ett mål i mina utbildningar är att komma förbi dessa frågor och se till att deltagarna blir bekväma i, förstår och kan tillämpa testdriven utveckling med säkerhet.
Hur blir man bra på TDD?
Som många andra saker kan man inte vara bra på något första gången man gör det. Det är inte så ofta vi ger oss tid att medvetet träna på något som är en del av vårt yrke. Ännu mer sällan funderar vi på hur man bli bra på något. Josh Kaufman skriver i sin utmärkta bok “De första 20 timmarna - hur man bli bra på vad som helst - fort” att en av de viktigaste principerna för att kunna lära sig något fort är att dekonstruera området.
Så här följer min dekonstruktion av TDD:
- Disciplin - TDD är ett metodiskt angreppssätt på kodning
- Testfall - med TDD driver vi vår design med hjälp av tester
- Isolering - TDD innebär test av “enheter” i isolering
- Kodbearbetning - TDD ger oss möjligheten att se till att koden alltid är “bra”
Man kan förstås både hitta andra förmågor och andra skärningar, men dessa har gett mig ett angreppssätt som är lättare att ta sig an och utbilda kring. Men det gör det också lättare att förstå att ingen kan bli bra på TDD på första försöket. Det är många saker som hänger ihop.
För att det skall bli lite lättare att träna på TDD med min dekonstruktion så kommer här en kort beskrivning av vad jag menar med de fyra områdena.
Disciplin
TDD fungerar bäst när vi kommer in i en rytm. Den rytmen består av många olika delar men i botten ligger “röd-grön-refaktorera”. Dvs. skriv ett test och se att det fallerar, implementera bara så mycket att existerande tester, inklusive det du just skrev, går igenom. Städa sedan upp.
Det finns många positiva effekter av sambanden mellan flera olika mikro-regler och för att dessa skall sätta sig i ryggmärgen behöver vi disciplin nog att hålla oss till dessa.
Att det går långsamt i början är förstås frustrerande. TDD är ungefär som “touch-typing”, i början går det fortare att köra pekfingervalsen och det kräver inte så lite disciplin att hålla kvar fingersättningen när det i början går frustrerande långsamt. Men det är nog inte heller många pekfingervalsare som når 100 WPM.
Kreativa infall har stor betydelse i programutveckling så vi måste både fånga dom och ändå avsluta det vi höll på med. Det kräver både disciplin och tekniker för att hantera det.
Testfall
Ett inte ovanligt missförstånd om TDD är att man skriver alla, eller många, tester först. Så är det inte tänkt. Istället väljer vi ett test, eller exempel, utifrån vad vi tror är “rätt” just nu, och implementerar det och bara så mycket att samtliga tester vi nu har passerar.
Att veta vad som är “rätt” test just nu kräver dels förmåga att strukturera upp testfall, fånga idéer och insikter om nya under tiden men också en känsla för vad som kan vara ett bra val precis nu.
När jag utbildar i TDD lägger jag mycket tid på resonemang och tekniker i just detta område. Om testerna är vår mejsel som vi skall knacka fram vår programvarumässiga marmorstaty med så vill det ju till att den är skärpt och vi vet hur vi ska använda den för att få rätt effekt.
Isolering
Att kunna köra samtliga relevanta testfall på sekund(er) är viktigt för att man inte skall bli distraherad medan man väntar på återkopplingen. Det sägs att om vi måste vänta mer än en halv sekund så börjar vår hjärna tänka på annat, i detta fall, börjar koda igen utan att vänta på återkopplingen från vårt förra steg. Tillräckligt snabb återkoppling kan vi bara få om vi har korta byggtider, så lite extra kod som skall laddas och köras som möjligt och kan göra det direkt i utvecklingsmiljön.
TDD fungerar bäst när man helt isolerar enheten under test. Förmågan och kunskapen kring hur man gör det är en väsentlig del i att kunna utnyttja TDD. Det går naturligtvis att i viss utsträckning använda TDD-principer även när man har s.k. integrerande tester (flera, eller alla, enheter länkade tillsammans) också men då dessa ofta är mycket långsammare och mer komplexa bryter de vårt fokus och flyt.
Hur man isolerar sin enhet och vad en enhet är varierar förstås mycket mellan olika språk och miljöer så här kan man träna och utvecklas länge, varje gång man byter projekt blir en chans att träna.
Kodbearbetning
Det krävs ett ständigt “knådandet” av koden för att vår kod hela tiden skall vara “bra”. Det krävs den trygghet som bara tillräckligt med tester, snabba tester, kan ge oss för att våga göra det hela tiden. Basen i det är förstås teknikerna för refaktorering (“Refactoring - Improving the design of existing code”, Martin Fowler). Många editorer och utvecklingsmiljöer har stöd för många av dessa nu för tiden, men vi behöver både känna till de olika refaktoreringarna och veta när de kan tillämpas, både fram- och baklänges.
Det finns också mycket annat i det här området, från “code smells”, namngivning, lagom bra design till Clean Code, design- och arkitekturmönster. Om man tränar på dessa aspekter så kommer man allt mer att förstå god design, känna igen mönster som börjar uppenbara sig, och veta hur man kan hjälpa koden att bli det den behöver bli.
Nyttan?
Ja, vad är det som är den stora nyttan med TDD? För mig personligen är det känslan av trygghet som gör att man ständigt vågar prova sig fram, göra den ändring man tror behövs och supersnabbt få återkopplingen på det. Ett misstag är lätt att backa från eftersom man tar så små steg. Det gör att man känner att man är på rätt väg hela tiden och inte fastnar i långa sessioner av felsökning.
Men i det större perspektivet leder det till system som är välstrukturerade, vältestade och lätta att ändra. Eller, som Erik Dietrich skrev, “it comes with a lot of collateral good”.