Enhetsprovning Succinctly Se innan du hoppar

Detta är ett utdrag från Unit Testing Succinctly eBook, av Marc Clifton, vänligt tillhandahållen av Syncfusion.

De tidigare artiklarna har berört en mängd olika problem och fördelar med enhetstestning. Denna artikel är en mer formaliserad titt på kostnaden och fördelarna med enhetstestning.

Enhetstestkod vs kod som testas

Din enhetstestkod är en separat enhet från koden som testas, men den delar många av samma problem som krävs av din produktionskod:

  • Planera
  • Utveckling
  • Testning (ja, enhetstester måste testas)

Dessutom kan enhetstest också:

  • Har en större kodbas än produktionskoden.
  • Behöver synkroniseras när produktionskoden ändras.
  • Tenderar att tillämpa arkitektoniska riktningar och genomförandemönster.

Enhetstestkoden kan vara större än produktionskoden

När man bestämmer om testen kan skrivas mot en enda metod, bör man överväga:

  • Validerar kontraktet?
  • Fungerar beräkningen korrekt?
  • Är objektets interna tillstånd korrekt inställt?
  • Returnerar objektet ett "sane" tillstånd om ett undantag uppstår?
  • Testas alla kodvägar?
  • Vilken inställning eller nedbrytningskrav har metoden?

Man borde inse att linjenummeret för kod för att testa även en enkel metod kan vara betydligt större än linjenummet för själva metoden.


Underhålla enhetsprov

Byte av produktionskod kan ofta ogiltiga enhetstester. Kodförändringar faller i stort sett i två kategorier:

  • Ny kod eller ändringar i befintlig kod som förbättrar användarupplevelsen.
  • Betydande omstrukturering för att stödja krav som den befintliga arkitekturen inte stöder.

Den förra bär vanligtvis litet eller inget underhållskrav på befintliga provningar. Den senare kräver emellertid ofta ett omfattande omarbetande av enhetsprov, beroende på förändringens komplexitet:

  • Refactoring konkreta klassparametrar till gränssnitt eller abstrakta klasser.
  • Refactoring klasshierarkin.
  • Byta ut en tredjepartsteknik med en annan.
  • Refactoring koden för att vara asynkron eller stöduppgifter.
  • Övrigt:
    • Exempel: Att byta från en konkret databasklass som SqlConnection till IDbConnection, så att koden stöder olika databaser och kräver omarbetning av enheten testar de samtalsmetoder som var beroende av konkreta klasser för deras parametrar.
    • Exempel: Ändra en modell för att använda ett standard serialiseringsformat, till exempel XML, snarare än en anpassad serialiseringsmetodik.
    • Exempel: Om du byter från en intern ORM till en ORM från en tredje part, t.ex. Entity Framework, kan det krävas avsevärda förändringar i uppställningen eller nedbrytningen av enhetstest.

Enhetsprovning förverkligar en arkitekturparadigm?

Som tidigare nämnts, testar enhetstestning, särskilt i en testdriven process, vissa minimala arkitektur- och implementeringsparadigmer. För att ytterligare stödja lättheten att sätta upp eller riva ner vissa områden i koden kan enhetsprovning också dra nytta av mer komplicerade arkitekturhänsyn, såsom inversion av kontroll.


Enhetstestresultat

I åtminstone bör de flesta klasser underlätta mocking av något föremål. Detta kan avsevärt förbättra testens prestanda, t ex att testa en metod som utför utländska nyckelintegritetskontroller (istället för att förlita sig på databasen för att rapportera fel senare) inte kräva en komplicerad inställning eller nedräkning av testscenariot i databasen sig. Dessutom bör det inte kräva att metoden faktiskt frågar databasen. Dessa är alla prestationsfel i testet och lägger till beroende på en live, autentiserad anslutning till databasen, och kan därför inte hantera en annan arbetsstation som kör exakt samma test samtidigt. I stället, genom att mocka databasanslutningen, kan enhetstestet enkelt konfigurera scenariot i minnet och överföra anslutningsobjektet som ett gränssnitt.

Det är dock inte nödvändigtvis bara den bästa praxis att mocka en klass, det är kanske bättre att refactorera koden så att all information som metoden behöver erhålls separat, vilket skiljer ut förvärvet av data från beräkningen av data. Nu kan beräkningen utföras utan att hysa objektet som ansvarar för att skaffa data, vilket ytterligare förenklar testinställningen.


Mitigating Kostnader

Det finns ett par kostnadsminskande strategier som bör övervägas.

Korrekt ingångar

Det mest effektiva sättet att minska kostnaden för enhetsprovning är att undvika att skriva provet. Medan detta låter självklart, hur uppnås detta? Svaret är att se till att data som skickas till metoden är korrekt - med andra ord - korrekt inmatning, korrekt utdata (omvänd "skräp i skräp"). Ja, du vill noga med att testa beräkningen själv, men om du kan garantera att kontraktet är uppfyllt av den som ringer, finns det inget särskilt behov av att testa metoden för att se om den hanterar felaktiga parametrar (överträdelser av kontraktet).

Det här är en liten höjd eftersom du inte har någon aning om hur metoden kan kallas i framtiden. Du kanske vill att metoden fortfarande ska validera sitt kontrakt, men i sammanhanget i vilket det för närvarande används, om du kan garantera att kontraktet alltid är uppfyllt, så finns det ingen riktig poäng i skriftliga tester mot kontraktet.

Hur säkerställer du korrekta ingångar? För värden som kommer från ett användargränssnitt är det lämpligt att filtrera och styra användarens interaktion för att för-filtrera värdena. Ett mer sofistikerat tillvägagångssätt är att definiera specialiserade typer snarare än att förlita sig på allmänna ändamålstyper. Tänk på Divide-metoden som beskrivits tidigare:


public static int Divide (int täljare, int nämnare) om (nämnare == 0) släng nytt ArgumentOutOfRangeException ("nämnaren kan inte vara 0.");  returräknare / nämnare; 

Om nämnaren var en specialiserad typ som garanterade ett icke-nollvärde:

offentlig klass NonZeroDouble protected int val; public int Value get return val;  set if (value == 0) släng nytt ArgumentOutOfRangeException ("Värdet kan inte vara 0.");  val = värde; 

Divide-metoden skulle aldrig behöva testa för detta fall:

///  /// Ett exempel på att använda typspecificitet för att undvika ett kontraktstest. ///  statisk statisk int Divide (int täljare, NonZeroDouble-nämnare) retur täljare / nämnare.Value; 

När man anser att detta förbättrar applikationens typspecifika egenskaper och fastställer (förhoppningsvis) återanvändbara typer, inser man hur man undviker att skriva en massa enheterstester eftersom kod ofta använder typer som är för generella.

Undvik undantag från tredje part

Fråga dig själv - ska min metod vara ansvarig för hantering av undantag från tredje part, till exempel webbtjänster, databaser, nätverksanslutningar etc.? Det kan hävdas att svaret är "nej". Beviljas, detta kräver ytterligare arbete framåt. API: n för tredje part (eller till och med ram) behöver en omslag som hanterar undantaget och en arkitektur där Ansökan kan rullas tillbaka när ett undantag uppstår och bör antagligen genomföras. Men dessa är förmodligen värda förbättringar av ansökan ändå.

Undvik att skriva samma test för varje metod

De tidigare exemplen-korrekta ingångarna, specialiserade typsystem, undviker undantag från tredje part - allt driver problemet till mer allmänt ändamål och eventuellt återanvändbar kod. Detta hjälper till att undvika att skriva samma eller liknande kontraktsvalidering, test av undantagshanteringsenhet och låter dig istället fokusera på test som validerar vad metoden ska göra under normala förhållanden, det vill säga själva beräkningen.


Kostnadsfördelar

Som tidigare nämnts finns det bestämda kostnadsfördelar för enhetstestning.

Kodning till kravet

En av de uppenbara fördelarna är processen att formalisera de interna kodkraven från externa användbarhet / processkrav. När man går igenom denna övning är riktningen med den övergripande arkitekturen vanligen en sidovinst. Mer konkret, utveckla en serie test som ett specifikt krav är uppfyllt från enhet perspektiv (snarare än integrationsprov perspektiv) är ett objektivt bevis för att koden uppfyller kravet.

Minskar nedströms fel

Regressionstestning är en annan (ofta mätbar) fördel. När kodbasen växer, kontrollerar man att den befintliga koden fortfarande fungerar som avsedd sparar avsevärd manuell testtid och undviker "oops, vi testade inte för det" scenariot. Vidare, när ett fel rapporteras kan det korrigeras omedelbart, vilket ofta sparar andra medlemmar i laget den stora huvudvärk att undra varför något som de lita på plötsligt inte fungerar korrekt.

Testfall Ge en form av dokumentation

Enhetstester verifierar inte bara att en metod hanterar sig korrekt när det ges dåliga ingångar eller undantag från tredje part (som beskrivits tidigare, försök att minska dessa typer av test), men också hur metoden förväntas fungera under normala förhållanden. Detta ger värdefull dokumentation till utvecklare, särskilt nya teammedlemmar - via enhetstestet kan de enkelt ta upp inställningskrav och användarfall. Om ditt projekt genomgår en betydande arkitektonisk refactoring kan de nya enhetstesterna användas för att styra utvecklare vid omarbetning av deras beroende kod.

Förtvinga en arkitekturparadigm Förbättrar arkitekturen

Som tidigare beskrivits, en mer robust arkitektur genom användningen av gränssnitt, inversion av kontroll, specialtyper etc.-som alla underlättar enhetstestning-också förbättra programmets robusthet. Krav förändras, även under utveckling, och en genomtänkt arkitektur kan hantera dessa förändringar betydligt bättre än en applikation som inte har någon eller liten arkitektonisk övervägning.

Juniorprogrammerare

I stället för att ge en junior programmerare ett krav på hög nivå som ska genomföras på programmets skicklighetsnivå kan du i stället garantera en högre nivå av kod och framgång (och ge en lärarupplevelse) genom att ha den yngre programkursen implementeringen mot testet snarare än kravet. Detta eliminerar mycket dåliga rutiner eller gissningar som en junior programmerare slutar att genomföra (vi har alla varit där) och minskar det arbete som en högre utvecklare behöver göra i framtiden.

Kodrecensioner

Det finns flera typer av kodrecensioner. Enhetstester kan minska hur mycket tid som används för att granska koden för arkitektoniska problem eftersom de tenderar att tillämpa arkitektur. Enhetstesterna validerar dessutom beräkningen och kan också användas för att validera alla kodvägar för en given metod. Detta gör kodrecensioner nästan onödiga - enhetstestet blir en självgranskning av koden.

Konverteringskrav till test

En intressant bieffekt av att konvertera extern användbarhet eller processkrav till formaliserade kodtester (och deras stödjande arkitektur) är att:

  • Problem med kraven upptäcks ofta.
  • Arkitektoniska krav är uppenbarade.
  • Förutsättningar och andra luckor i kraven identifieras.

Dessa upptäckter, som ett resultat av enhetstestprocessen, identifierar problem tidigare i utvecklingsprocessen, vilket vanligtvis bidrar till att minska förvirring, omarbeta och därigenom sänker kostnaden.