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.
I forbindelse med enhetstesting har en enhet flere egenskaper.
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:
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 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:
SqlClient
fabrikkleverandørens forekomst.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.
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.
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:
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.
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.
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".
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.
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.
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:
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.
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.
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 testingVidere, 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.
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.
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.
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.
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:
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.