Inlägg i denna serie, ”Anledningar att enhetstesta”:
Anledning 1 – Dokumentationseffekten
Anledning 2 – Säkerhetsbälteseffekten
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.
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
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.
Det 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 bilar – med 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!
Taggar: programmering, enhetstester, enhetstestning, tdd, legacy code