2008-02-20

Testa först eller testa sist?

Den senaste tiden har jag både följt och själv deltagit i diskussioner kring huruvida man bör skriva sina automatiserade tester först eller om det går lika bra att skriva dem efter att man skrivit koden. För den som inte är bekant med TDD, testdriven utveckling (Test Driven Development), kanske det låter väldigt främmande att skriva ett test för koden innan man skriver själva koden som uppfyller testet (läs i så fall lite mer här). För andra kan det verka som hårklyverier eller åtminstone reducerbart till en smaksak vilket sätt man väljer.

De flesta som arbetar testdrivet (vilket jag åtminstone i denna text betraktar som synonymt med "testa först" och TDD) är emellertid överens om att det faktiskt är av stor betydelse att skriva testet först. När man redan innan koden skrivs är tvungen att tänka på hur den ska anropas blir det gränssnitt/API man utvecklar tydligare och får en bättre design. Om testet skrivs först kan man också vara säker på att koden man skriver faktiskt kommer att vara testbar, något som kan vara nog så svårt att åstadkomma i efterhand på grund av beroenden till andra klasser, initialisering av rätt testdata o.s.v. Dessutom undviker man den i många sammanhang överhängande risken med att skriva testerna sist: att de inte blir skrivna alls på grund av tidsbrist, lättja etc.

Standardiseringsorganisationen IEEE publicerade nyligen en rapport i en av sina tidskrifter, "On the Effectiveness of Test-first Approach to Programming", som jämförde produktiviteten hos team som skrev testerna först med team som skrev dem sist. Slutsatsen från den studien är att de som jobbar testdrivet tenderar att skriva fler tester och att de som skriver fler tester i regel är mer produktiva. Författarna bakom studien förklarade den högre produktiviteten för de testdrivna teamen med ett antal synergieffekter hos detta angreppssätt:

  • Bättre förståelse för uppgiften eftersom utvecklarna måste uttrycka funktionaliteten otvetydigt i form av ett test innan de börjar programmera.
  • Bättre fokus på uppgiften eftersom man måste bryta ned uppgifterna i små och lätthanterliga delar när man skriver testet först.
  • Snabbare inlärningsprocess då för grova improduktiva nedbrytningar av funktionalitet snabbt överges för mer produktiva finkorniga sådana för att kunna testas.
  • Mindre omarbetning eftersom problem upptäcks direkt när testerna körs varje gång man lagt till eller ändrat i kod och man då vet att det man precis gjorde introducerade problemet. Felaktigheter är billiga att åtgärda direkt då problemet och koden fortfarande är färska i utvecklarnas minne.

Okej, så det finns en hel del goda skäl att skriva testerna först, att driva designen med tester. Varför skulle man då vilja skriva testerna sist istället? Ett vanligt skäl är att man helt enkelt inte har förstått fördelen med TDD ännu och att det, i det vattenfallstänk som fortfarande tycks dominera mjukvaruutvecklingen, verkar mer naturligt att förlägga testerna till slutet av projektet eller iterationen som ett sorts "sluttest" för att allt fungerar som det är tänkt.

Bland annat denna dominerande föreställning om vad tester är och när och hur de bör genomföras, liksom den traditionella uppdelningen mellan testare och utvecklare i olika roller och personer, har gjort att många börjat söka efter andra sätt att förklara TDD. Istället för ”tester” talar man om ”exempel” eller ”specifikationer” eftersom det egentligen säger mer om arbetssättet och gör det lättare att förstå varför man skriver dem först och vilka fördelar det kan ha. Detta alternativa synsätt kallas ofta BDD, Behaviour Driven Development, men dessa intressanta frågor får vänta till en senare bloggpost.

Ett annat argument mot att skriva testerna först är att man då skriver för många tester. Förutom att det därmed krävs en större arbetsinsats och att det blir mer kod att överblicka, kan man hamna i en situation där testkoden håller produktionskoden ”gisslan” (tack för den metaforen Daniel Brolund!) genom att en förändring i koden leder till väldigt många brutna tester och därmed förändringar i även dessa tester. Då får man plötsligt motsatsen till ett av de önskade resultaten: koden blir mer komplex och svårare att ändra.

Ett motargument är att bara dåligt utförd TDD skapar sådan intim koppling mellan enhetstesterna och produktionskoden att detta blir ett stort problem. Naturligtvis kommer du vara tvungen att ändra testerna när du ändrar produktionskoden, men så är det oavsett när du testar och det är dessutom något positivt: testerna visar dig att existerande funktionalitet påverkas.

Men om man skriver testerna sist går man ju miste om fördelar som den positiva effekten på design och bättre fokus genom små steg? Kanske inte, TDD passar inte nödvändigtvis alla och det finns säkert utvecklare som har lättare för att få fram god design och bra kod på andra vägar, särskilt om man har god erfarenhet av att ha jobbat framgångsrikt på andra sätt. Och bara för att man skriver testerna efter koden innebär det inte att man måste jobba med stora steg och skriva alla tester vid slutet av en iteration. Många som jobbar med enhetstester men inte med TDD beskriver en arbetsprocess där de skriver kod och tester ”samtidigt” och inte går vidare med nästa klass eller metod förrän tester är på plats.

Kanske är det också här en del av problemet ligger i debatten kring testa först eller testa sist; man jämför gärna sin valda metods fördelaktiga sidor med den andra metodens misslyckanden. TDD-evangelister utgår ofta från att den som ska övertygas inte jobbar med enhetstester överhuvudtaget och kanske att de producerar kod med mindre god design som är svårtestad och full med beroenden (och varför skulle de inte det med tanke på hur det ofta ser ut :-)). Eller så tittar man på hur arbetet med enhetstester traditionellt sett ut och ser då att de skrivs alldeles för sent, riskerar att inte bli skrivna o.s.v. De som vill testa sist har i sin tur kanske sett misslyckade exempel på omoget införande av TDD i ett projekt med lång inlärningstid och med undermåliga tester som hållit koden gisslan.

Här skulle jag, kanske något resignerat, kunna avsluta texten med att ”bra utvecklare kommer alltid att skriva bra kod oavsett metod” och "välj det som passar bäst i den specifika situationen". Det ligger naturligtvis en hel del sanning i dessa klichéer och de kan dessutom tålas att upprepas så att man än en gång påminns om att vara ödmjuk inför andras erfarenheter och val. Jag tror dock att testa först är det bästa arbetssättet helt enkelt därför att det uppmuntrar dig att följa annan god praxis. Visst, du kan åstadkomma god design och bra API:er utan TDD, du kan bryta ned program i små testbara delar och minimera beroenden utan TDD, du kan skriva bra enhetstester i efterhand, du kan köra testerna kontinuerligt och refaktorisera din kod utan TDD och du kan ha disciplin nog att aldrig fuska och spara testerna till senare. Men varför chansa? Varför inte använda ett arbetssätt som mer eller mindre tvingar dig att göra allt detta? Om det sedan inte är helt lätt att lära sig, ja då beror det kanske på att de goda praktiker som det i sin tur tvingar fram inte heller är helt lätta att lära sig, men när du väl har gjort det kommer du att vara en bättre utvecklare.

Inga kommentarer: