Kategorisering af softwaretests

Automatiske tests er uundværlige.

De giver tryghed når der sker ændringer. Uanset om kilden til ændringerne er os selv fordi at vi ønsker at refaktorere, vores stakeholder fordi de ønsker nye funktioner eller fordi at vi har afhængigheder til eksterne systemer og komponenter der løbende bliver opgraderet.

Testene betyder at vi hurtigt kan få feedback på de ændringer vi laver – og at vi ikke skaber utilsigtede sideeffekter. Men over tid begynder vores testsuite at vokse – kodestumper kan stadig testes hurtigt i isolation men når det kommer til kørsel af flere tests bliver det sværere at lægge det rette snit.

Med kategorier har man en måde at fokusere på en del af testsuiten og ignore en anden, men hvilke kategorier skal man vælge og hvornår skal de hver især afvikles?

Det er afhængigt af situationen hvor lang tid vi ønsker at bruge på at afvikle testsuiten – har vi lavet en enkelt lille kodeændring dybt nede i systemet ønsker vi sikkert ikke at afvikle alle grænsefladetests før senere.

Der er forskellige tidspunkter hvor vi gerne vil kunne vælge dele af testsuiten til og fra:

  • Efter en samling af ændringer/opgraderinger.
  • Efter merge med kodeændringer fra andre i teamet.
  • Før deployment af artefakter.
  • I fast interval, fx. hver nat.

Hvis vores testsuite indeholder tests der tager længere tid at udføre giver det mening kun at udføre en del af dem på visse hændelser og det er her kategorisering af tests giver mening.

For at tilknytte en test til en kategori dekoreres den med metainformation – fx. med NUnit i C#:

[Test, Category("unit")]
public void Can_lookup_car()
{
	...
}

Det er helt individuelt hvilke kategorier man vælger at bruge. Mange bruger et sæt af kategorier i stil med disse:

Unit

En unit-test er en test der verificerer en eller flere funktioner eller objekter i isolation. Testen bør ikke medføre noget IO og vil derfor være hurtig at udføre. Ofte er der behov for at skabe en kunstig kontekst for at undgå at teste for meget i form at mocks/stubs.

Tests i denne kategori bør være så hurtige at udføre, at vi kan køre alle indeholdte tests ved samtlige af ovenstående hændelser.

Typisk eksekveringtid pr. test < 500 millisekunder

Component

En component-test bryder princippet om isolation. Tests i denne kategori må gerne medføre direkte eller indirekte IO og hvis denne IO er permanent kan der være behov for at udføre oprydning efter kørsel. Der kan stadig være behov for at skabe en kunstig kontekst for kun at teste et bestemt snit i systemet.

Fordi at denne kategori tager lidt længere at udføre kan man godt forsvare at man først kører samtlige component-tests ved check-in og merge.

Typisk eksekveringtid pr. test < 10 sekunder

Denne kategori bliver også tiltider kaldt for integration.

System

En system-test tester det fulde system typisk gennem den endelige grænseflade. Dermed kræves at alle komponenter af systemet er kørende, er der tale om et website skal der startes en webserver, skal der bruges databaser kræves en databaseserver osv. Automatiserede test af grænseflader gennem fx. Selenium tilhører denne kategori.

Disse tests bør køres før deployment for at checke at systemet opfører sig som det skal før slutbrugeren rammer det. Ud over dette kan man passende køre det asynkront i fast interval så man løbende får hints om systemets tilstand.

Typisk eksekveringtid pr. test < 1 minut

Andre kategorier

Da hver test godt kan tilhøre flere kategorier, kan man med fordel bruge ekstra kategorier til at markere tests der presser grænserne for fx. eksekveringstid. Det kunne være en LongRunning-kategori til de få component-tests der tager længere tid og som man måske ønsker at ekskludere i den normale kørsel og kun køre asynkront – det kunne fx. være en performance-test der belaster forskellige dele af systemet og dermed kun ønskes kørt én gang dagligt.


En testsuite der kan udføres på kort tid gør det nemt og trykt at gennemføre refaktoreringer og afprøve nye ideer. Kategorisering af tests gør at vi efter behov kan fravælge dele af vores suite og opnå en hurtig eksekvering.

Jeg er helt for at man ikke bruger en masse kræfter på at strukturere ens testsuite. Det er helt ok at man løbende tilføjer nye tests hist og her i suiten efter behov – at bruge en masse tid på at strukturere disse giver ingen værdi.

Eksempelvis kan to klasser eller funktoner med præcist det samme interface have vidt forskelligt behov for tests pga. kompleksiteten af den opgave de udfører. Her skal man lade ens ordensgen acceptere en hvis form for kaos og asymmetri.

Lige så vel som formålet med vores tests ikke bør være symmetri, bør det heller ikke være andre irrationelle størrelser såsom KLOC eller code-coverage men korrekt produktionskode og indirekte højere udviklingshastighed.

Omvendt mener Jay Fields at vore tests er for svære at vedligeholde og skal på dette års GOTO-konference i København argumentere for at vi har brug for en mere effektiv måde at strukturere vore tests – en session jeg hellere må se.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *