Utenfra-og-inn programmering


tirsdag 25. august 2009 Testing / TDD

Outside-in development, eller utenfra-og-inn programmering som jeg kaller det, er et av elementene i Dan Norths Behavior-Driven Development (BDD). Mens testdreven utvikling (TDD) i bunn og grunn bare betyr at du skal skrive tester før du skriver kode, forteller BDD gjennom utenfra-og-inn prinsippet hvordan du bør designe systemet ditt ved hjelp av testdreven utvikling (eller Code-by-Example, som Dan kaller det).

Prinsippet er ganske enkelt: Du begynner med det du vet at du trenger – det kravspesifikasjonen eller User Storien sier at du skal lage – adferden du skal produsere. For eksempel blir du bedt om å lage en webside hvor man kan administrere ett eller annet.., la oss si at vi skal administrere lagerbeholdningen i en butikk. Ok, da begynner vi ytterst med brukergrensesnittet.

Vi definerer da gjerne et View, som har ansvar for å vise lagerbeholdningen, og la brukeren gjøre operasjoner mot denne (legge til/slette/etc). Og så arbeider vi oss innover. Vi lager typisk en controller/presenter som har som oppgave å populere viewet med data og håndtere kommandoer fra brukeren via viewet. Når vi har den på plass ser vi at vi trenger mer – data fra en repository f.eks., så da lager vi den.

Gjennom denne prosessen oppdager vi de objektene brukergrensesnittet trenger å sammarbeide med, de objektene vi trenger på neste nivå, de objektene vi trenger på nivået etter det - slik fortsetter det til vi er i mål og alt fungerer. På denne måten følger vi YAGNI-prinsippet (You Ain't Gonna Need It), fordi all koden vi skriver enten brukes av brukeren direkte (viewet i dette tilfellet), eller av annen kode som allerede er skrevet. Alt vi gjør gir dermed verdi her og nå.

rect3489

En historie fra virkeligheten..

Kontrasten til denne fremgangsmåten er typisk at vi begynner med en datamodell, gjerne i form av en relasjonsdatabase, og arbeider oss utover. Vi tror vi vet hva vi trenger i neste ledd, og lager en modell vi håper passer. Etterhvert som vi arbeider oss utover i lagene må vi hele tiden tilpasse oss det som ligger innenfor.

I forrige uke skulle vi løse en oppgave hvor vi skulle ta imot en innkommende SMS-melding, ta noen avgjørelser basert på meldingens innhold og tidligere hendelser i systemet, og sende ut en ny SMS.

Jeg brukte TDD, og begynte med å lage en "meldingshåndterer" som tok imot SMS'en. Jeg jobbet meg innover og oppdaget andre ansvar som håndtereren trengte; en klasse for å lese meldingsinnholdet, en klasse for å ta avgjørelser basert på innholdet, en klasse jeg kunne bruke for å spørre om tidligere hendelser, en klasse som kunne konstruere nye meldinger etc.

Min kollega begynte i andre enden. Han definerte et par databasetabeller, et par entitetsklasser, og laget en repository med metoder for CRUD-operasjoner mot tabellene og mapping mot entitetene. Da vi etter ikke alt for lang tid sammenlignet hva vi hadde gjort, så vi raskt at det var en klar mismatch. Logikken jeg hadde laget hadde ikke noe behov for alle CRUD-operasjonene min kollega hadde laget – jeg trengte bare noen få og dessuten mye enklere metoder. Og entitetsklassene ble forkastet. Utenfra-og-inn hadde hjulpet meg til å lage et design som var mer fokusert på adferden vi skulle levere. Modellen man tror man trenger når man tenker innenfra-og-utover stemmer ofte ikke med det designet testdreven utvikling gir deg.

I ALT.NET miljøet sier man ofte at du kan/bør designe hele systemet ditt før du oppretter databasen. Databasen er der bare for å persistere tilstand, eller ta vare på historikk for rapportering. Den er ikke essensiell for programvaren. De bruker uttrykket persistence ignorance – forretningslogikken, kjernen i programvaren, skal ikke bry seg om hvordan data lagres. Dette står i sterk kontrast til hvordan de fleste utviklingsprosjekter jeg har vært borti foregår, hvor det første man gjør når man skal begynne på en ny feature er å definere hvordan tabellen skal se ut.

"Mocking"

mockery For å gjøre utenfra-og-inn programmering på en effektiv måte må man gå i små steg, og få tilbakemelding hele tiden gjennom testene man skriver. Hvis man skal utvikle alle klassene man trenger for å tilfredstille en feature før man kan sjekke om den fungerer som forventet, gjør man det ekstremt vanskelig for seg selv. Det er her mocking kommer inn i bildet.

(Illustrasjonen til høyre er hentet fra Michael Feathers og Steve Freemans foredrag Test Driven Development: Ten Years Later. Anbefales som en historisk oppsummering av TDD.)

Det vi populært kaller mocking betyr at når vi utvikler en betsemt enhet/klasse/modul så isolerer vi oss bort fra avhengighetene til denne enheten/klassen/modulen. I eksempelet mitt hvor jeg skal lage et webgrensesnitt for å administrere lagerbeholdning vil jeg ikke tenke på repository-klassen når jeg utvikler controlleren. Men controlleren trenger helt klart en repository. Vi lager da en falsk repository som controlleren kan bruke i testene vi skriver.

Når vi er ferdig med controlleren, og skal utvikle repository-klassen, er det repositoriens avhengigheter vi "mocker ut". Følger man dette helt strickt, vil en feil i en testklasse alltid korrespondere til en feil i den éne testede klassen. Klassen som testes/utvikles isoleres fra resten av systemet.

Jeg praktiserte lenge TDD helt uten mocking, og klarte ikke se verdien av å begynne med det. Jeg trodde mocking var noe man gjorde om man ikke klarte å gjøre TDD på en orntlig måte. Det var først når jeg begynte å følge Model-View-Presenter (og lignende presentasjonsmønstre) at jeg så verdien, og nå bruker jeg mocking hele tiden. I min neste blogpost vil jeg gå mye mer i dybden på dette, og snakke om hvilke muligheter man har tilgjengelig for avhengighetsisolering i .NET.

Konklusjon

Utenfra-og-inn er en måte å kode på hvor vi driver frem designet av systemet gjennom tester. Vi går i små steg, og gjør oss ferdige med det vi holder på med før vi går videre. Ting vi ser vi trenger underveis mocker vi. Gjennom denne fremgangsmåten skiller vi bedre hva vi trenger fra hvordan det skal løses – vi fokuserer på det logiske designet og adferd, og det fysiske designet (implementasjonen om du vil) er underordnet og kommer etterhvert. Vi drives primært av forretningsbehov, og vokser systemet vårt organisk med de tingene vi oppdager at vi trenger.

Dette er uten tvil en herlig måte å jobbe på, og alle som ikke kjenner seg igjen i denne fremgangsmåten bør forsøke den.

Knagger: , , , ,


comments powered by Disqus