Gå til hovedsiden

Isolert utvikling i en integrert verden

Svært mange sitter på hjemmekontor, noe som medfører at utviklere merker at avhengigheter i applikasjonene deres plutselig er mer utilgjengelige, noe man vanligvis ikke tenker over. 

visma.no/blogg/wp-content/uploads/sites/4/2020/04/UtviklerVismaConsulting

I dette innlegget skal jeg belyse utfordringer med avhengigheter og hvordan vi tester vår nyutviklede kode, samt vise en mulig måte å separere integrasjoner fra kjerneapplikasjonen vår for å forenkle utviklingen.

Scenario: Saksbehandlingsløsning

For å eksemplifisere noen vanlige utfordringer i utvikling, skal jeg bruke et fiktivt saksbehandlingssystem. I dette systemet kan søkere sende inn søknader i en portal, saksbehandlere behandler saker og gjør vedtak i en annen portal, og søkere kan klage på vedtaket i den første portalen. Saksgangen arkiveres i et eksternt system, og søkere autentiserer seg gjennom ID-porten. Som utvikler har du nettopp endret kvitteringssiden for innsendt klage. For å teste dette, må du gjennom følgende steg:

  1. Logg inn i søknadsportalen
  2. Send inn en søknad
  3. Søknaden prosesseres og opprettes i
    1. Saksbehandlingssystemet
    2. Arkivet
  4. Logg inn i saksbehandlingsportalen
  5. Behandle søknaden og fatt vedtak
    1. Vedtaket sendes til arkivet
  6. I søknadsportalen: Opprett en klage
    1. Klagen sendes til arkivet
  7. Verifisér endringen på kvitteringssiden

Denne testprosessen har to store utfordringer:

  • De uthevede elementene er “integrerte hendelser”, da de er avhengige av systemer utenfor applikasjonen vi faktisk jobber med. Dette gjør oss sårbare: Hva skjer hvis arkivløsningen er nede? Hvordan kommer vi oss videre hvis innlogging er utilgjengelig? Hvordan sjonglerer vi søknadsportalen og saksbehandlingssystemet slik at vi kommer i tilstanden vi ønsker?
  • Steg 1-6 er irrelevante for det vi nettopp utviklet. Det er kun hindre vi må komme oss forbi for å gjøre det vi faktisk vil, nemlig steg 7. Dette kan utgjøre mye bortkastet tid i utviklingen*

Dette oppsettet medfører altså både kompleks testing og risiko. Jeg skal nå presentere tre steg for å håndtere dette slik at man kan holde fokus på den delen av applikasjonen man jobber på her og nå.

*Noen vil kanskje argumentere med at det er bra å teste hele verdikjeden, da dette kan avdekke ukjente feil. Jeg sier ikke at man ikke skal teste hele verdikjeder, men all testing bør gjøres med intensjon. Dette er ikke tilfelle her.

På utkikk etter ny jobb? Sjekk ut våre ledige stillinger

1. Isolasjon

Vi starter med å lage stubs for integrasjonene. I første omgang kan man bestemme oppførsel med en enkel if-setning:

if(IsDevelopmentEnvironment()){
	UseStub();
} else {
	UseRealApi();
}

Denne lille kodesnutten har et stort problem, som vi skal adressere om et øyeblikk. Først vil jeg bare påpeke hva vi kan oppnå med dette. Dersom vi lager stubs for alle integrasjonene våre, kan vi starte applikasjonen i “isolert modus”. For eksempel kan vi lage en stub som erstatter innlogging og gir oss en enkel bruker med basisdata, og en stub som erstatter arkivet og returnerer OK på alt vi sender til den. Vi har da fjernet de fleste hindrene skissert ovenfor, og kan fortsette utviklingen selv om integrasjonene våre er utilgjengelige.

Problemet med kodesnutten er at vi blander test-og produksjonskode i samme prosjekt. Dette innfører kompleksitet og risiko: Hvordan sikrer vi at applikasjonen vår ikke går inn i “isolert modus” i produksjon? Hvordan tester vi applikasjonen vår i “integrert modus” under utvikling? Verktøy som kompilatorflagg kan hjelpe her, men en mer solid løsning krever en annen struktur.

Last ned: Guide til effektiv applikasjonsforvaltning

2. Separasjon

Dette steget handler om å separere test-koden fra produksjonskoden. Det gjør vi ved å opprette et nytt web-prosjekt (eller tilsvarende for din plattform) og flytter stubbene og annen eventuell testkode hit. Du henter frem IoC-containeren din (som jeg tar utgangspunkt i at du allerede bruker i prosjektet ditt) og erstatter de eksterne avhengighetene dine med stubbene. I noen tilfeller kan det være nødvendig å bruke middleware eller andre verktøy for å oppnå ønsket oppførsel.

Avhengig av hvilken teknologi man bruker, kan dette steget by på noen utfordringer. I bunnen av denne artikkelen er et eksempel i .NET Core. Det som er viktig er at applikasjonen fortsatt skal bruke all produksjonskode (unntatt integrasjonene), og at man ikke skal duplisere kode.

Vi kan nå velge om vi vil bruke testprosjektet og “isolert modus” eller produksjonsprosjektet og “integrert modus”, samtidig som produksjonskoden er fri for testfunksjonalitet. I testprosjektet kan vi i tillegg legge til seed-data og designe stubs slik at vi enklest og raskest mulig kommer oss til funksjonaliteten vi ønsker å teste.

3. Bonus-steg: Automatisering

Som et siste steg i oppryddingen kan vi opprette et eget testprosjekt og flytte testkoden hit. Web-prosjektet vi opprettet tidligere vil kun inneholde det som trengs for å starte opp applikasjonen. I testprosjektet vårt kan vi skrive isolerte integrasjonstester der vi utforsker avhengighetene våre og verifiserer at de fortsatt oppfører seg slik vi forventer. Noen teknologier lar deg også starte applikasjonen i minnet slik at man kan kjøre tester mot applikasjonen vår i “isolert modus”. Dersom man klarer å lage et slikt oppsett kan man få svært god verdi ut av testene sine: med integrasjonstester som verifiserer avhengigheter, og ende-til-ende-tester av applikasjonen i “isolert modus” har man dekket hele backend med relativt stabile tester.

En eksempel-implementasjon i .NET Core og med biblioteket TestHost for å kjøre applikasjonen i minnet finner du her.

Husk: man må ikke fikse alt på en gang. Hvis man kan fikse én avhengighet for 50% av tilfellene kan dette spare mye tid og risiko. Ta inventar, finn ut hva som gir mest verdi å fikse, og ta resten etter hvert.

Last ned: Guide til effektiv applikasjonsforvaltning av skreddersydde IT-løsninger

Klikk her for å laste ned guiden