Det kommer et punkt hvor det ikke lønner seg å teste mer, mener Christian Aamodt. (Bilde: Christian Aamodt)

Når bør du slutte å teste koden?

Christian Aamodt tviler på om det lønner seg å prøve å bli perfekt.

DEBATT: Etter drøye 10 år med stort fokus på automatisert testing, ønsker jeg å vurdere hvor dette har tatt oss, og om omfanget av automatisert testing legges på et fornuftig nivå.

Jeg har opplevd at dette er et tema som kan være vanskelig å diskutere med mange utviklere, da det har en lei tendens til å oppstå litt «religiøsitet» hos endel. Å argumentere for mindre bruk av automatisert testing kan lett bli som å argumentere for mindre «sikkerhet» på flyplasser.

Det skal betydelig argumentasjon til for at folk vil lytte, og det er mye lettere å få gjennomslag andre veien, for folk ønsker jo sikkerhet. Men man må spørre seg om alle rutinene på dagens flyplasser gir mer sikkerhet, eller om noen av rutinene er fåfengte forsøk på å få det.

I flere anbudsforespørsler de senere årene har det vært krav om at systemet skal ha «100 prosent testdekning».

Artikkel­forfatteren Christian Aamodt er Java-arkitekt og fagansvarlig mobile løsninger i Ciber.
Artikkel­forfatteren Christian Aamodt er Java-arkitekt og fagansvarlig mobile løsninger i Ciber.

Jeg er usikker på om de som krever dette vet hva de ber om. I en bokstavelig, teknisk forstand betyr dette at hver eneste kodede funksjon og «vei» i hver enkelt funksjon skal testes at den virker som forutsatt. Man ender da raskt opp med betydelig større mengder testkode enn reell kjørekode. Det synes også implisitt forutsatt at det ikke vil være feil i testkoden...

Jeg etterlyser et mer edruelig forhold til automatiserte tester. Jeg mener testing bør holdes på et nivå hvor den gjør mer nytte enn skade.

I Ciber Norge har vi vært gjennom mange Java-prosjekter med ulik grad av automatisert testing, og brukt ulike rammeverk for dette. Eksempler er jUnit, Selenium og FitNesse, og noen prosjekter har benyttet verktøy som Cobertura for å vurdere testdekningen i prosjektet. Våre prosjekter er ofte i en mellomstørrelse, total prosjektstørrelse mellom 1 000 og 10 000 timer, og gjerne for kunder i offentlig sektor. Prosjektene våre har brukt Test Driven Development (TDD) i varierende grad. Noen prosjekter har vært utført helt uten TDD, men har valgt å skrive tester for utvalgte deler av koden i etterkant.

Hva er fordelene med automatisert testing? Jeg vil her forsøke å trekke frem de viktigste. Vær oppmerksom på at jeg ikke her fokuserer på fordeler ved bruk av TDD, da dette kan være andre.

  • Man kan videreutvikle koden og være rimelig sikker på at det man gjør ikke bryter med de forutsetningene som er gjort i koden.
  • Bedre støtte ved vedlikehold av koden i ettertid. Dette gir spesielt store fordeler når utviklere byttes ut.
  • Man får gradvis luket ut feil ettersom man legger til nye tester når feil forekommer.
  • Man kan enkelt kjøre en «suite» av tester som gjør manuell regresjonstesting mindre omfattende.
  • Testene forenkler samkjøring av flere utviklere som jobber mot samme kodebase.

De fleste utviklere er enige om at automatiserte tester gir fordelene nevnt ovenfor. Men ulik bruk av automatiserte tester gir ulik grad av disse fordelene i forhold til hvor mye tid eller arbeid som legges ned i å utvikle og vedlikeholde testene.

Som i eksemplet ovenfor om sikkerhet på flyplasser, vil det alltid være mulig å legge til nye og mer omfattende rutiner for å skape sikkerhet, men på et eller annet nivå vil den økte sikkerheten ikke stå i forhold til ulempene det medfører for passasjerene å gjennomføre de aktuelle rutinene. Man kunne for eksempel nekte all form for glassprodukter eller metallbestikk etter sikkerhetskontrollen, slik at disse ikke kan benyttes til å kapre fly. Dette vil medføre betydelige problemer for restauranter og taxfreesalg, og vil neppe gi den helt store sikkerhetseffekten. Dette avspeiler seg i «Law of diminishing returns». Se illustrasjonen øverst i denne artikkelen: Kurven som viser når det er hensiktsmessig å avslutte innsatsen, fordi økt innsats ikke vil gi økt utbytte.

Det er ikke nødvendigvis slik at behovet for kontroll er det samme i alle prosjekter. Det som kan gi god og ønsket kontroll i et stort prosjekt med mange utviklere, kan være en hemsko i et mindre prosjekt med få utviklere. Et eksempel her kan være et webprosjekt med et lite og oversiktlig brukergrensesnitt som tar kort tid å teste manuelt. Hvis man i en slik situasjon benytter et webtestrammeverk for å teste frontend, kan det skape betydelige merkostnader ved videreutvikling eller modernisering, uten at testoppsettet nødvendigvis ga så mye merverdi selv når det ble satt opp første gang.

En omfattende «suite» av automatiserte tester kan også gjøre det mer arbeidskrevende å refaktorere moduler i koden.

Noen vil nok innvende at man med fornuftig strukturering av koden vil kunne kjøre de samme testene på modulen når den er ferdig refaktorert. I en ideell verden ville jeg vært enig, men ofte er årsaken til refaktoreringen nettopp at modulen eller grensesnittet til denne ikke ble strukturert og utviklet på beste vis i første runde. Full testdekning i modulen vil da gjøre refaktoreringen mer arbeidskrevende, og i mange situasjoner vil det i tillegg til refaktorering av selve koden være nødvendig med omfattende refaktorering av testkoden.

Arbeidet som utføres for å forhindre feil, i dette tilfellet automatisert testing, bør også til en viss grad ses opp mot konsekvensen av feil.

I noen prosjekter, for eksempel ved utvikling av IT-systemer for sykehusene som i verste fall kan bety liv eller død for pasientene, må man ha et annet forhold til konsekvensene av feil enn hvis man utvikler spill for barn. Jeg mener ikke å bagatellisere feil i mindre kritiske systemer, og jeg er helt innforstått med prinsipper for «craftsmanship», men det må alltid gjøres avveininger i forhold til økonomi i prosjektet eller om man skal prioritere funksjonalitet fremfor utvidet testsuite.

En innvending mot automatisert testing har vært at testene gir en slags falsk trygghet for de som skal videreutvikle koden. Dette er mulig, uten at jeg har sett klare tegn til det.

Uansett er det en forutsetning at de som skal videreutvikle koden har full forståelse for hvordan koden henger sammen. En solid testsuite kan gi en god «dokumentasjon» av koden, slik at det er lettere å få forstå hvordan den er ment å virke. En middels god eller utdatert testsuite kan skape mer forvirring enn oversikt. Man må derfor være klar over at testene og mockede objekter må videreutvikles i parallell med koden. En slurvet holdning her vil fort føre til at testsuiten forvirrer mer enn den hjelper.

Kvaliteten på testkoden er like viktig som kvaliteten på kjørekoden.

Jeg har sett eksempler på at man for å kunne teste deler av koden, åpner opp koden mer enn man ellers ville gjort. I slike tilfeller fører testingen til at man har mindre kontroll enn man ville hatt uten testene. Kvalitetssikring av testkoden er altså viktig, og må ikke nedvurderes i forhold til kvalitetssikring av kjørekoden.

Finnes det noe alternativ til automatisert testing? Nei, ikke noe som oppnår helt det samme, men det er fremdeles mulig å benytte mange kjente, kjære teknikker og prinsipper for å utvikle kode som har liten sannsynlighet for feil. I endel tilfeller vil dette være godt nok, i hvert fall på deler av koden.

Hva er så et fornuftig nivå for automatisert testing?

Som nevnt er dette avhengig av systemet som skal utvikles, dets størrelse og i hvilken grad konsekvensen av feil er kritisk. Hvis man forutsetter at prosjektet skal utvikle et system som ikke dreier seg om liv og død ved feil, og er i en størrelsesorden 5 000 til 10 000 timer, vil jeg anbefale følgende strategi for (automatisert) testing og kvalitet:

  • Legg forretningslogikken i modellen, og test denne.
  • Sørg for at så mye som overhodet mulig av forretningslogikken ligger i domenemodellen. Ha denne som en separat del av koden (for eksempel som eget prosjekt i utviklingsverktøyet). Etterstreb høy testdekning (med jUnit eller lignende) av den viktige koden i denne delen av systemet (det vil si ikke test «gettere og settere» og slikt, men der hvor det finnes kode med risiko for feil).
  • Trekk ut generelle funksjoner, og test disse.
  • Sørg for at alle funksjoner som kan generaliseres på fornuftig vis, blir generalisert, og at resten av koden benytter denne mer generelle koden. Hvis du så sørger for at denne koden blir testet, unngår du mange feil uten å utforme mengder av testkode.
  • Bruk «Single Responsibility Principle», og lukk koden.
  • Strukturer koden din slik at klassene i så stor grad som mulig har et rent og oversiktlig grensesnitt mot resten av applikasjonen. Sørg for å beskytte klassevariable for påvirkning utenfra. Hvis det er vanskelig å teste all funksjonaliteten i klassen uten å åpne opp med ekstra metoder eller å gjøre klassevariable tilgjengelige utenfra, er det ofte et tegn på at det kan være fornuftig å omstrukturere klassen til mindre klasser.
  • Gjør det lett å finne ut hva en feil kommer av.
  • Sørg for nullsjekking av parametere, slik at man lett kan identifisere hva som forårsaker feil. Hvis du er i tvil om en feilsituasjon noengang vil oppstå, og om du trenger å ta hensyn til den, kast alltid en exception som beskriver hva som har skjedd.
  • Kvalitetssikre all testkode på samme nivå som kjørekoden.
  • Vurder om enkelte deler av applikasjonen er godt nok testet.
  • I mange applikasjoner er det funksjoner som er kritisk i sin natur. I en nettbutikk er det for eksempel kritisk å ha på plass rutiner som sørger for at ikke priser er null eller betydelig lavere enn det korrekte tallet. Vurder om det finnes slike caser i din applikasjon, og om det trengs ekstra rutiner som forhindrer slike feil.

Jeg mener man ved en slik tankegang vil kunne utvikle systemer som er rimeligere, og hvor automatisk testing står i et fornuftig forhold til systemets behov.

Til toppen