Unit Testing Succinctly Se før du hopper

Dette er et utdrag fra Unit Testing Succinctly eBook, av Marc Clifton, gitt vennlig av Syncfusion.

De forrige artiklene har berørt en rekke bekymringer og fordeler med enhetstesting. Denne artikkelen er en mer formalisert titt på kostnadene og fordelene ved enhetstesting.

Enhetstestkode vs Kode blir testet

Enhetstestkoden din er en egen enhet fra koden som blir testet, men den deler mange av de samme problemene som kreves av produksjonskoden din:

  • Planlegger
  • Utvikling
  • Testing (ja, enhetstester må testes)

I tillegg kan enhetstester også:

  • Har en større kodebase enn produksjonskoden.
  • Trenger synkronisering når produksjonskode endres.
  • Tendens til å håndheve arkitektoniske retninger og implementeringsmønstre.

Unit Test Code Base kan være større enn produksjonskode

Når man skal avgjøre om testene kan skrives mot en enkelt metode, bør man vurdere:

  • Validerer kontrakten?
  • Fungerer beregningen riktig?
  • Er den interne tilstanden til objektet satt riktig?
  • Returnerer objektet til en "sane" tilstand hvis et unntak oppstår?
  • Blir alle kodebaner testet?
  • Hvilke oppsett eller teardown krav har metoden?

Man bør innse at linjestellingen til kode for å teste selv en enkel metode kan være betydelig større enn linjene i selve metoden.


Opprettholde Unit Tests

Endring av produksjonskoden kan ofte ugyldiggjøre enhetstester. Kodeendringer faller grovt i to kategorier:

  • Ny kode eller endringer i eksisterende kode som forbedrer brukeropplevelsen.
  • Vesentlig restrukturering for å støtte krav som den eksisterende arkitekturen ikke støtter.

Den førstnevnte bærer vanligvis lite eller ingen vedlikeholdskrav på eksisterende enhetstester. Sistnevnte krever imidlertid betydelig omarbeidelse av enhetstester, avhengig av forandringens kompleksitet:

  • Refactoring konkrete klasseparametre til grensesnitt eller abstrakte klasser.
  • Refactoring klassen hierarkiet.
  • Bytte ut en tredjepartsteknologi med en annen.
  • Refactoring koden for å være asynkron eller støtteoppgaver.
  • andre:
    • Eksempel: Endre fra en konkret databaseklasse som SqlConnection til IDbConnection, slik at koden støtter forskjellige databaser og krever omarbeidelse av enheten, tester de samtalemetodene som var avhengig av konkrete klasser for deres parametre.
    • Eksempel: Endre en modell for å bruke et standard serialiseringsformat, for eksempel XML, i stedet for en tilpasset serialiseringsmetode.
    • Eksempel: Endring fra en intern ORM til en tredjeparts ORM, for eksempel Entity Framework, kan kreve betydelige endringer i oppsettet eller nedrivningen av enhetstester.

Går Unit Testing Enforce a Architecture Paradigm?

Som nevnt tidligere håndterer enhetstesting, spesielt i en testdrevet prosess, visse minimal arkitektur- og implementeringsparadigmer. For ytterligere støtte for enkel å sette opp eller rive ned noen områder av koden, kan enhetstesting også ha nytte av mer komplekse arkitekturhensyn, som inversjon av kontroll.


Enhetsprøveytelse

I det minste bør de fleste klasser legge til rette for mocking av ethvert objekt. Dette kan forbedre testens ytelse betydelig, for eksempel å teste en metode som utfører utenlandske nøkkelintegritetskontroller (i stedet for å stole på databasen for å rapportere feil senere), bør ikke kreve et komplisert oppsett eller teardown av testscenariet i databasen seg selv. Videre bør det ikke kreve at metoden faktisk spørrer databasen. Disse er alle resultatene i testen og legger til avhengigheter på en levende, autentisert forbindelse med databasen, og kan derfor ikke håndtere en annen arbeidsstasjon som kjører nøyaktig den samme testen samtidig. I stedet, ved å mocke databasetilkoblingen, kan enhetstesten enkelt sette opp scenariet i minnet og sende forbindelsesobjektet som et grensesnitt.

Det er imidlertid ikke nødvendigvis den beste praksisen heller, men det er ikke nødvendigvis den beste øvelsen. Det kan være bedre å reflektere koden, slik at all informasjonen metoden trenger, oppnås separat, og separerer oppkjøpet av dataene fra beregningen av dataene. Nå kan beregningen utføres uten å mocke objektet som er ansvarlig for å skaffe dataene, noe som forenkler testoppsettet ytterligere.


Mitigating Kostnader

Det er et par kostnadsreduserende strategier som bør vurderes.

Korrekte innganger

Den mest effektive måten å redusere kostnadene ved enhetstesting er å unngå å skrive testen. Selv om dette lyder tydelig, hvordan oppnås dette? Svaret er å sikre at dataene som sendes til metoden, er korrekte - med andre ord - korrekt innspilling, korrekt utgang (omvendt av "søppel inn, søppel ut"). Ja, du vil nok fortsatt teste beregningen selv, men hvis du kan garantere at kontrakten er oppfylt av den som ringer, er det ikke noe spesielt behov for å teste metoden for å se om den håndterer feil parametere (brudd på kontrakten).

Dette er litt av en glatt skråning fordi du ikke har noen anelse om hvordan metoden kan bli kalt i fremtiden. Faktisk vil du kanskje at metoden fortsatt skal validere kontrakten, men i konteksten der det for tiden er brukt, hvis du kan garantere at kontrakten alltid er oppfylt, er det ikke noe reelt punkt i skriftlige tester mot kontrakten.

Hvordan sikrer du riktige innganger? For verdier som kommer fra et brukergrensesnitt, er det hensiktsmessig å filtrere og kontrollere brukerens samhandling for å forhåndsfilter verdiene. En mer sofistikert tilnærming er å definere spesialiserte typer i stedet for å stole på generelle formålstyper. Vurder Divide-metoden som er beskrevet tidligere:


offentlig statisk int Divide (int teller, int nevner) hvis (nevner == 0) kaste ny ArgumentOutOfRangeException ("Neminator kan ikke være 0.");  retur teller / nevner; 

Hvis nevneren var en spesialisert type som garanterte en nullverdier:

offentlig klasse NonZeroDouble protected int val; offentlig int verdi get return val;  sett if (verdi == 0) kaste nytt ArgumentOutOfRangeException ("Verdi kan ikke være 0.");  val = verdi; 

Divide-metoden trenger aldri å teste for dette tilfellet:

///  /// Et eksempel på bruk av type spesifisitet for å unngå en kontraktstest. ///  offentlig statisk int Divide (int teller, NonZeroDouble nevner) retur teller / nevner.Value; 

Når man vurderer at dette forbedrer typen spesifisitet av applikasjonen og etablerer (forhåpentligvis) gjenbrukbare typer, innser man hvordan dette unngår å måtte skrive en rekke enhetstester fordi kode ofte bruker typer som er for generelle.

Unngå tredjeparts unntak

Spør deg selv - skal metoden min være ansvarlig for å håndtere unntak fra tredjepart, for eksempel webtjenester, databaser, nettverksforbindelser, etc.? Det kan hevdes at svaret er "nei." Gitt, dette krever noe videre oppe arbeid - API-en for tredjepart (eller til og med ramme) trenger et omslag som håndterer unntaket og en arkitektur der den interne tilstanden til søknad kan rulles tilbake når et unntak oppstår og bør sannsynligvis bli implementert. Imidlertid er disse sannsynligvis verdifulle forbedringer av søknaden uansett.

Unngå å skrive de samme testene for hver metode

De tidligere eksempler-korrekte inngangene, spesialiserte typesystemer, unngår tredjeparts unntak - alle skyver problemet til mer generelle formål og muligens gjenbrukbar kode. Dette bidrar til å unngå å skrive samme eller lignende kontraktsvalidering, unntakshåndteringsenhetstester, og lar deg fokusere i stedet på tester som validerer hva metoden skal gjøre under normale forhold, det vil si selve beregningen.


Kostnadsfordeler

Som nevnt tidligere, er det endelige kostnadsfordeler for enhetstesting.

Koding til kravet

En av de åpenbare fordelene er prosessen med å formalisere de interne kodekravene fra eksterne bruks- / prosesskrav. Når man går gjennom denne øvelsen, er retning med den generelle arkitekturen vanligvis en sidevinst. Mer konkret, utvikle en pakke tester som et bestemt krav er oppfylt fra enhet perspektiv (i stedet for integreringstest perspektiv) er objektivt bevis på at koden utfører kravet.

Reduserer nedstrøms feil

Regresjonstesting er en annen (ofte målbar) fordel. Etter hvert som kodebasen vokser, bekrefter at eksisterende kode fortsatt fungerer som påtatt sparer betydelig manuell testtid og unngår "oops, vi testet ikke for det" scenariet. Videre, når en feil er rapportert, kan den korrigeres umiddelbart, og sparer ofte andre medlemmer av teamet den store hodepine av å lure på hvorfor noe de stolte på, plutselig ikke fungerer riktig.

Test tilfeller Gi en form for dokumentasjon

Enhetstester verifiserer ikke bare at en metode håndterer seg riktig når det er gitt dårlige innganger eller unntak fra tredjeparter (som beskrevet tidligere, prøv å redusere slike tester), men også hvordan metoden forventes å opptre under normale forhold. Dette gir verdifull dokumentasjon til utviklere, spesielt nye medarbeiderne - via enhetstesten kan de lett opprette oppsettskravene og brukstilfeller. Hvis prosjektet gjennomgår en betydelig arkitektonisk refactoring, kan de nye enhetstesterne brukes til å veilede utviklere ved å omarbeide deres avhengige kode.

Forsterke en arkitekturparadigm Forbedrer arkitekturen

Som beskrevet tidligere, en mer robust arkitektur gjennom bruk av grensesnitt, inversjon av kontroll, spesialiserte typer, etc. - som alle letter prøvetesting-også forbedre robustheten av applikasjonen. Kravene endres, selv under utviklingen, og en gjennomtenkt arkitektur kan håndtere disse endringene betydelig bedre enn en applikasjon som ikke har noen eller lite arkitektonisk vurdering.

Junior programmerere

I stedet for å gi en junior programmerer et krav på høyt nivå som skal implementeres på programmørens ferdighetsnivå, kan du i stedet garantere et høyere nivå av kode og suksess (og gi en lærerfaring) ved å ha den yngre programmeringsprogrammet implementeringen mot testen snarere enn kravet. Dette eliminerer mye dårlig praksis eller gjetning som en junior programmerer slutter å implementere (vi har alle vært der) og reduserer omarbeidet en høyere senior utvikler trenger å gjøre i fremtiden.

Kode Anmeldelser

Det finnes flere typer kodeanmeldelser. Enhetstester kan redusere mengden tid brukt til gjennomgangskode for arkitektoniske problemer fordi de pleier å håndheve arkitekturen. Videre styrker enhetstester beregningen og kan også brukes til å validere alle kodebaner for en gitt metode. Dette gjør kodeanmeldelser nesten unødvendige - enhetstesten blir en selvanmeldelse av koden.

Konverteringskrav til test

En interessant bivirkning ved å konvertere ekstern brukbarhet eller prosesskrav til formaliserte kodetester (og deres støttende arkitektur) er at:

  • Problemer med kravene oppdages ofte.
  • Arkitektoniske krav er opplyst.
  • Forutsetninger og andre hull i kravene er identifisert.

Disse funnene, som et resultat av enhetstestprosessen, identifiserer problemer tidligere i utviklingsprosessen, som vanligvis bidrar til å redusere forvirring, omarbeide og dermed reduserer kostnadene.