Unit Testing Succinctly Hva er Unit Testing?

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

Enhetstest handler om å bevise korrekthet. For å bevise at noe fungerer riktig, må du først forstå hva begge a enhet og a test Egentlig er det før du kan utforske hva som er bevisbart innenfor evnen til enhetstesting.

Hva er en enhet?

I forbindelse med enhetstesting har en enhet flere egenskaper.

Rene enheter

En ren enhet er den enkleste og mest ideelle metoden for å skrive en enhetstest. En ren enhet har flere egenskaper som muliggjør enkel testing.

En enhet bør (ideelt sett) ikke ringe andre metoder

Når det gjelder enhetstesting, bør en enhet først og fremst være en metode som gjør noe uten å kalle noen andre metoder. Eksempler på disse rene enhetene finnes i string og Matte klasser-de fleste utførte operasjoner stole ikke på noen annen metode. For eksempel, følgende kode (hentet fra noe forfatteren har skrevet)

offentlig ugyldig SelectedMasters () streng currentEntity = dgvModel.DataMember; streng navToEntity = cbMasterTables.SelectedItem.ToString (); DataGridViewSelectedRowCollection selectedRows = dgvModel.SelectedRows; StringBuilder qualifier = BuildQualifier (selectedRows); UpdateGrid (navToEntity); SetRowFilter (navToEntity, qualifier.ToString ()); ShowNavigateToMaster (navToEntity, qualifier.ToString ()); 

bør ikke betraktes som en enhet av tre grunner:

  • I stedet for å ta med parametere, får den verdiene som er involvert i beregningen fra brukergrensesnittobjekter, spesielt en DataGridView og en ComboBox.
  • Det gjør flere samtaler til andre metoder som potensielt er enheter.
  • En av metodene ser ut til å oppdatere skjermen, sammenkoble en beregning med en visualisering.

Den første grunnen peker på en subtil problem-egenskaper bør betraktes som metallsamtaler. Faktisk er de i den underliggende implementeringen. Hvis metoden din bruker egenskaper i andre klasser, er dette en slags metodeanrop og bør vurderes nøye når du skriver en passende enhet.

Realistisk er dette ikke alltid mulig. Ofte er det nødvendig å ringe til rammen eller noen annen API for at enheten skal kunne utføre sitt arbeid. Disse anropene bør imidlertid inspiseres for å avgjøre om metoden kan forbedres for å lage en renere enhet, for eksempel ved å trekke samtalene inn i en høyere metode og sende resultatene av samtalene som en parameter til enheten.

En enhet skal bare gjøre en ting

En følge av "en enhet bør ikke kalle andre metoder" er at en enhet er en metode som gjør en ting og bare en ting. Ofte kalles andre metoder for å gjøre det mer enn en ting-en verdifull ferdighet å vite når noe egentlig består av flere deltakere - selv om det kan beskrives som en høyt oppgave, noe som gjør at det høres ut som en enkelt oppgave!

Følgende kode kan se ut som en rimelig enhet som gjør en ting: den legger inn et navn i databasen.

offentlig int Insert (Person person) DbProviderFactory fabrikk = SqlClientFactory.Instance; bruker (DbConnection forbindelse = factory.CreateConnection ()) connection.ConnectionString = "Server = localhost; Database = myDataBase; Trusted_Connection = True;"; connection.Open (); bruker (DbCommand command = connection.CreateCommand ()) command.CommandText = "sett inn i PERSON (ID, NAME) verdier (@Id, @Name)"; command.CommandType = CommandType.Text; DbParameter id = command.CreateParameter (); id.ParameterName = "@Id"; id.DbType = DbType.Int32; id.Value = person.Id; DbParameter name = command.CreateParameter (); name.ParameterName = "@Name"; name.DbType = DbType.String; navn.Size = 50; name.Value = person.Name; command.Parameters.AddRange (ny DbParameter [] id, navn); int rowsAffected = command.ExecuteNonQuery (); returnere raderAffected; 

Men denne koden gjør faktisk flere ting:

  • Å skaffe en SqlClient fabrikkleverandørens forekomst.
  • Instantiere en tilkobling og åpne den.
  • Instantiere en kommando og initialisere kommandoen.
  • Opprette og legge til to parametere til kommandoen.
  • Utfør kommandoen og returner antall rader som er berørt.

Det er en rekke problemer med denne koden som diskvalifiserer det fra å være en enhet og gjør det vanskelig å redusere til grunnleggende enheter. En bedre måte å skrive denne koden på kan se slik ut:

offentlig int RefactoredInsert (Person person) DbProviderFactory fabrikk = SqlClientFactory.Instance; bruker (DbConnection conn = OpenConnection (fabrikk, "Server = localhost; Database = myDataBase; Trusted_Connection = True;")) bruker (DbCommand cmd = CreateTextCommand (conn, "sett inn i PERSON (ID, NAME) Navn) ")) AddParameter (cmd," @Id ", person.Id); AddParameter (cmd, "@Name", 50, person.Name); int raderAffected = cmd.ExecuteNonQuery (); returnere raderAffected;  beskyttet DbConnection OpenConnection (DbProviderFactory fabrikk, streng connectString) DbConnection conn = factory.CreateConnection (); conn.ConnectionString = connectString; conn.Open (); returnere conn;  beskyttet DbCommand CreateTextCommand (DbConnection conn, streng cmdText) DbCommand cmd = conn.CreateCommand (); cmd.CommandText = cmdText; cmd.CommandType = CommandType.Text; returner cmd;  beskyttet tomt AddParameter (DbCommand cmd, streng paramName, int paramValue) DbParameter param = cmd.CreateParameter (); param.ParameterName = paramName; param.DbType = DbType.Int32; param.Value = paramValue; cmd.Parameters.Add (param);  beskyttet ugyldig AddParameter (DbCommand cmd, streng paramName, int størrelse, streng paramValue) DbParameter param = cmd.CreateParameter (); param.ParameterName = paramName; param.DbType = DbType.String; param.Size = size; param.Value = paramValue; cmd.Parameters.Add (param); 

Legg merke til hvordan, i tillegg til å se renere, metodene OpenConnection, CreateTextCommand, og AddParameter er mer egnet til enhetstesting (ignorerer det faktum at de er beskyttede metoder). Disse metodene kan bare én ting og, som enheter, testes for å sikre at de gjør det riktig. Fra dette blir det lite poeng å teste RefactoredInsert metode, som det er helt avhengig av andre funksjoner som har enhetstester. I beste fall kan man skrive noen unntakshåndteringstest, og muligens noen validering på feltene i Person bord.

Korrekt korrekt kode

Hva om høyere-nivå-metoden gjør noe mer enn bare å ringe andre metoder som det er enhetstester, si, en slags ekstra beregning? I så fall skal koden som utfører beregningen flyttes til sin egen metode, tester skal skrives for det, og igjen kan metoden på høyere nivå stole på at koden er riktig. Dette er prosessen med å konstruere korrekt korrekt kode. Korrektheten på høyere nivå metoder forbedres når alt de gjør er å ringe nedre nivå metoder som har bevis (enhetstester) av korrekthet.

En enhet burde ikke (ideelt sett) ha flere kodebaner

Syklomatisk kompleksitet er bane av enhetstesting og applikasjonstesting generelt, da det øker vanskeligheten ved å teste alle kodebanene. Ideelt sett vil en enhet ikke ha noen hvis eller bytte om uttalelser. Kroppen av disse uttalelsene skal betraktes som enhetene (forutsatt at de oppfyller de andre kriteriene for en enhet) og skal testes, bør hentes ut i egne metoder.

Her er et annet eksempel tatt fra forfatterens MyXaml-prosjekt (del av parseren):

hvis (tagName == "*") foreach (XmlNode node i topElement.ChildNodes) hvis (! (node ​​er XmlComment)) objectNode = node; gå i stykker;  foreach (XmlAttribute attr i objectNode.Attributes) if (attr.LocalName == "Name") nameAttr = attr; gå i stykker;  annet ... etc ...

Her har vi flere kodebaner som involverer hvis, ellers, og for hver uttalelser, som:

  • Opprett oppsettkompleksitet, så mange betingelser må oppfylles for å utføre den indre koden.
  • Opprett testkompleksitet, da kodebanene krever forskjellige oppsett for å sikre at hver kodebane testes.

Det er åpenbart at betinget forgrening, sløyfer, saksopplysninger etc. ikke kan unngås, men det kan være verdt å vurdere refactoring av koden slik at internaler av forholdene og sløyfer er separate metoder som kan testes uavhengig. Da kan testene for høyere nivåmetoden ganske enkelt sikre at tilstandene (representert av forhold, looper, brytere etc.) håndteres riktig, uavhengig av beregningene de utfører.

Avhengige enheter

Metoder som har avhengighet i andre klasser, data og statsinformasjon er mer komplekse å teste fordi disse avhengighetene oversetter til krav for instantiated objekter, eksistens av data og forhåndsbestemt tilstand.

forutsetninger

I sin enkleste form har avhengige enheter forutsetninger som må oppfylles. Enhetstestmotorer gir mekanismer for å oppnå testavhengighet, både for individuelle tester og for alle tester i en testgruppe, eller "fixtur".

Faktiske eller simulerte tjenester

Kompliserte avhengige enheter krever at tjenester som databasetilkoblinger blir instantiated eller simulert. I det tidligere kodeeksemplet, er Sett inn Metoden kan ikke testes enhet uten evne til å koble til en faktisk database. Denne koden blir mer testbar hvis databasens interaksjon kan simuleres, typisk ved bruk av grensesnitt eller grunnklasser (abstrakt eller ikke).

De refactored metodene i Sett inn kode beskrevet tidligere er et godt eksempel fordi DbProviderFactory er en abstrakt grunnklasse, slik at man lett kan lage en klasse som kommer fra DbProviderFactory å simulere databaseforbindelsen.

Håndtering av eksterne unntak

Avhengige enheter, fordi de ringer til andre APIer eller metoder, er også mer skjøre, de må kanskje eksplisitt håndtere feil som potensielt genereres av metodene de kaller. I den tidligere kodesammenstillingen Sett inn Metodekode kan pakkes inn i en prøvefeltboks, fordi det er sikkert mulig at databasetilkoblingen kanskje ikke eksisterer. Unntakshandleren kan komme tilbake 0 for antall berørte rader, rapportering av feilen gjennom en annen mekanisme. I slike situasjoner må enhetstester være i stand til å simulere dette unntaket for å sikre at alle kodebaner utføres korrekt, inkludert å fange og endelig blokker.


Hva er en test?

En test gir en nyttig påstand om enhetens korrekthet. Tester som hevder at en enhet er riktig, utøver vanligvis enheten på to måter:

  • Tester hvordan enheten fungerer under normale forhold.
  • Tester hvordan enheten fungerer under unormale forhold.

Normale betingelser Testing

Testing av hvordan enheten fungerer under normale forhold er uten tvil den enkleste testen som skal skrives. Når alt kommer til alt, når vi skriver en funksjon, skriver vi enten det for å tilfredsstille et eksplisitt eller implisitt krav. Gjennomføringen gjenspeiler en forståelse av dette kravet, som delvis omfatter hva vi forventer som input til funksjonen og hvordan vi forventer at funksjonen skal opptre med disse inngangene. Derfor tester vi resultatet av funksjonen gitt forventede innganger, om resultatet av funksjonen er en returverdi eller en tilstandsendring. Videre, hvis enheten er avhengig av andre funksjoner eller tjenester, forventer vi også at de skal oppføre seg riktig og skriver en test med den underforståtte antakelsen.

Unormale betingelser Testing

Det er mye vanskeligere å teste hvordan enheten fungerer under unormale forhold. Det krever å bestemme hva en unormal tilstand er, som vanligvis ikke er åpenbar ved å inspisere koden. Dette gjøres mer komplisert når du tester en avhengig enhet - en enhet som forventer en annen funksjon eller tjeneste for å oppføre seg riktig. I tillegg vet vi ikke hvordan en annen programmerer eller bruker kan utøve enheten.


Enhetstester og andre testpraksis

Enhetstesting som en del av en omfattende testmetode

Enhetstest erstatter ikke andre testpraksis; Den bør utfylle andre testpraksis, og gi tilleggsdokumentasjonstøtte og tillit. Figur 1 illustrerer ett konsept av "applikasjonsutviklingsstrømmen" - hvordan annen testing integreres med enhetstesting. Merk at kunden kan være involvert i et hvilket som helst stadium, men vanligvis ved godkjenningstestprosedyren (ATP), systemintegrasjon og brukervennlighet.

Sammenlign dette med V-modellen av programvareutviklings- og testprosessen. Selv om det er relatert til vannfallsmodellen for programvareutvikling (som i siste instans alle andre programvareutviklingsmodeller er enten en delmengde eller en utvidelse av), gir V-modellen et godt bilde av hva slags testing som kreves for hvert lag av programvareutviklingsprosessen:

V-modellen for testing

Videre, når et testpunkt mislykkes i noen annen testpraksis, kan et bestemt stykke kode vanligvis identifiseres som ansvarlig for feilen. Når det er tilfelle, blir det mulig å behandle dette stykket kode som en enhet og skrive en enhetstest for først å opprette feilen og, når koden er endret, for å bekrefte løsningen.

Godkjennelsesprøveprosedyrer

En godkjenningsprøveprosedyre (ATP) brukes ofte som et kontraktsmessig krav for å bevise at visse funksjoner har blitt implementert. ATPs er ofte knyttet til milepæler, og milepæler er ofte knyttet til utbetalinger eller ytterligere prosjektfinansiering. En ATP skiller seg fra en enhetstest fordi ATP demonstrerer at funksjonaliteten med hensyn til hele linjeposten er implementert. For eksempel kan en enhetstest avgjøre om beregningen er riktig. Imidlertid kan ATP validere at brukerelementene er gitt i brukergrensesnittet og at brukergrensesnittet viser resultatet av beregningen som spesifisert av kravet. Disse kravene er ikke dekket av enhetstesten.

Automatisert brukergrensesnitttesting

En ATP kan i utgangspunktet skrives som en serie brukergrensesnitt (UI) -interaksjoner for å verifisere at kravene er oppfylt. Regresjonstesting av søknaden etter hvert som den fortsetter å utvikle seg, gjelder også for test av enhetene samt godkjenningstesting. Automatisert brukergrensesnitt testing er et annet verktøy helt skilt fra enhetstesting som sparer tid og arbeidskraft, samtidig som testfeil reduseres. Som med ATP, erstatter enhetstester på ingen måte verdien av automatiserte brukergrensesnitttester.

Usability og User Experience Testing

Enhetstester, ATPer og automatiserte brukergrensesnitt tester ikke på noen måte brukervennlighetstesting - legger applikasjonen foran brukerne og får sin "brukeropplevelse" tilbakemelding. Usability testing bør ikke være om å finne beregningsfeil (bugs), og er derfor helt utenfor utførelsen av enhetstester.

Ytelse og belastningstesting

Noen enhetstesteringsverktøy gir et middel for å måle ytelsen til en metode. For eksempel rapporterer Visual Studios testmotor på kjøretid, og NUnit har attributter som kan brukes til å verifisere at en metode kjøres innenfor en tildelt tid.

Ideelt sett bør et enhetstestverktøy for .NET-språk eksplisitt implementere ytelsestest for å kompensere for just-in-time (JIT) kodeoppstilling første gang koden kjøres.

De fleste belastningstestene (og tilhørende ytelsestester) er ikke egnet for enhetstester. Visse former for belastningstester kan også gjøres med enhetstesting, i hvert fall til begrensningene i maskinvare og operativsystem, for eksempel:

  • Simulerer minnebegrensninger.
  • Simulere ressursbegrensninger.

Imidlertid krever slike tester ideelt støtte fra rammen eller OS API for å simulere slike belastninger for applikasjonen som testes. Å tvinge hele operativsystemet til å konsumere en stor mengde minne, ressurser eller begge, påvirker alle programmene, inkludert enhetstestprogrammet. Dette er ikke en ønskelig tilnærming.

Andre typer belastningstesting, for eksempel simulering av flere forekomster av å kjøre en operasjon samtidig, er ikke kandidater for enhetstesting. For eksempel er det sannsynligvis ikke mulig å teste ytelsen til en webtjeneste med en belastning på en million transaksjoner per minutt ved hjelp av en enkelt maskin. Selv om denne typen test enkelt kan skrives som en enhet, vil den faktiske testen innebære en serie testmaskiner. Og til slutt har du bare testet en svært smal oppførsel av webtjenesten under svært spesifikke nettverksforhold, som på ingen måte faktisk representerer den virkelige verden.

Av denne grunn har ytelse og belastningstesting begrenset anvendelse med enhetstesting.