När du förstått poängen med testdriven utveckling kommer givetvis krånglet. Så hur tar man sig vidare?
Vi börjar med poängen så att vi är överens om vad vi menar.
Testdriven utveckling (TDD) säger att man först skriver ett test som fallerar (viktigt), sedan implementerar man så att det inte längre fallerer.
I det läget tar man sig en funderare om man tycker att designen och kodstrukturen är enklast möjliga. Om inte så fixar man till den, utan att ändra vad koden gör, refactor på engelska. Eftersom de tester man har, går igenom så är det tryggt att ändra implementationen.
Vi fortsätter sedan med ett nytt varv, test – implementation – refactor.
Enkelt så långt, men vad är poängen? Det finns flera poänger.
För det första så beskriver ett test vad som skall göras medan implementationen beskriver hur det ska göras. Testerna blir därmed specifikationer skrivna i ett formellt språk, Java (eller vad som gäller för projektet).
För det andra, eftersom testet skrivs innan implementationen så blir designen av gränssnittet gjort utifrån en brukare (testet) vilket blir bättre än om det görs med utgångspunkt i hur implementationen är utformad.
För det tredje så blir det av att skriva tester vilket inte är fallet om man skriver dem efteråt. Täckningsgraden för tester skrivna efteråt blir dessutom betydligt lägre eftersom det oftast blir svårt att komma åt delar av implementationen.
Motivationen att skriva tester för kod som uppfattas som fungerande är extremt låg.
Att det finns automatiskt exekverande tester är en kritisk framgångsfaktor. All kod behöver förändras, men kan säga att underhåll av en kodrad egentligen börjar så fort den är skriven.
Med automatiserade tester kan jag ändra i befintlig kod med större trygghet. Testerna specificerar ju vad som är korrekt så om jag ändrar något som går utanför det, så märker jag det direkt.
Det var essensen, så vad är då kruxet?
Det absolut vanligaste problemet med införandet av TDD är att det finns väldigt mycket kod som inte har automatiska tester. När man vill införa TDD så står denna kodmängd framför en som ett oöverstigligt berg.
Tanken är att nästa ändring som jag inför ska jag göra testdrivet, d v s skriva ett test som visar vad som ska hända, konstatera att det inte händer och sedan ändra så att det händer. Men koden jag ska ändra i, den har inga tester och är inte förberedd för det!
Så antingen struntar jag i TDD och ändrar ändå, som förut. Tar det försiktigt och rör ingenting som verkar krångligt. Kör lite informella tester och hoppas att testarna ska upptäcka eventuella fel. Förmodligen gör de det och så får jag tillbaka problemet någon tid senare när jag hunnit glömma alldeles för mycket. Eller så biter jag ihop för jag vill inte gå den vägen en gång till.
Det får kosta om det vill, bara jag slipper idissla varje ändring flera gånger.
Nu infinner sig problemet att arkitekturen är inte utformad så att jag lätt kan införa tester. Den komponent jag ska ändra i är beroende av flera andra komponenter som jag inte tänkt mig skulle behöva blandas in. I synnerhet som dessa i sin tur är beroende av ytterligare andra komponenter.
Här krävs att jag tar till något som isolererar komponenten från omgivande komponenter. Det finns flera knep, bl a olika ramverk som simulerar komponenter (mocking) och bara skickar tillbaka fördefinierade resultat. Min favorit för Java är Mockito.
Ibland räcker det dock med att ärva eller implementera gränssnitt och definera metoder som skickar givna svar.
Tyvärr behöver vi något mer. Även om vi lyckas begränsa komponenten så att den inte anropar kringliggande komponenter så vet vi inte om vi har haft sönder den eftersom vi inte har någon formell definition på "trasig" respektive "fungerande".
Vad vi kan göra då är att ta ett fingeravtryck. Innan vi ändrar något så skriver vi ett eller flera tester som är bara till för att generera utdata. Vi försöker skapa ett avtryck av hur komponenten beter sig. När vi sedan ändrar i komponenten så kan vi se om utdata förändrats på något oväntat sätt. I så fall kan vi gå in och analysera vad det berodde på och om det ska anses vara fel eller rätt.
Några böcker:
"Test Driven", av Lasse Koskela.
"Working effectively with Legacy Code", av Michael Feathers
Klockren sammanställning av utmaningarna som en vanlig utvecklare ställs inför!
En mycket bra sammanfattning! Särskilt att kruxet även tas med.
Jag hoppas kunna sprida länken till ditt inlägg vidare till kunder och kollegor.
Jag är själv mycket intresserad av hur man kan få till TDD på legacy kod, eftersom jag ser så stora svårigheter med det.
Jag tilltalas dock mycket av TDD och särskilt av tanken att det medför att specifikation av moduler kodmässigt hamnar på ett samlat ställe.