I denne opplæringen vil jeg lære deg alt grunnleggende om idiomatisk testing i Go ved å bruke de beste metodene utviklet av språkdesignerne og samfunnet. Hovedvåpenet vil være standardprøvepakken. Målet vil være et prøveprogram som løser et enkelt problem fra Project Euler.
Go er et utrolig kraftig programmeringsspråk, lære alt fra å skrive enkle verktøy for å bygge skalerbare, fleksible webservere i hele kurset.
Summen kvadratforskjellen problemet er ganske enkelt: "Finn forskjellen mellom summen av kvadrater av de første hundre naturlige tall og kvadratet av summen."
Dette bestemte problemet kan løses ganske konsistent, spesielt hvis du kjenner din Gauss. For eksempel er summen av de første N naturlige tallene (1 + N) * N / 2
, og summen av kvadrater av de første N heltallene er: (1 + N) * (N * 2 + 1) * N / 6
. Så hele problemet kan løses med følgende formel og tildele 100 til N:
(1 + N) * (N * 2 + 1) * N / 6 - ((1 + N) * N / 2) * ((1 + N) * N / 2)
Vel, det er veldig spesifikt, og det er ikke mye å teste. I stedet opprettet jeg noen funksjoner som er litt mer generelle enn hva som trengs for dette problemet, men kan tjene til andre programmer i fremtiden (prosjektet Euler har 559 problemer akkurat nå).
Koden er tilgjengelig på GitHub.
Her er signaturene til de fire funksjonene:
// MakeIntList () -funksjonen returnerer en rekke sammenhengende heltallene // starter fra 1 hele veien til nummeret (inkludert nummeret) func MakeIntList (nummer int) [] int // Funksjonen squareList () tar et stykke av tall og returnerer en // array av quares av disse heltallene func SquareList (tall [] int) [] int // SumList () -funksjonen tar et snitt av heltall og returnerer summen func SumList (tallene [] int) int // Løs prosjekt Euler # 6 - Sum kvadratforskjell func Prosess (nummer int) int
Nå, med vårt målprogram på plass (vennligst tilgi meg, TDD-nidkjærere), la oss se hvordan du skriver tester for dette programmet.
Testpakken går hånd i hånd med gå test
kommando. Pakketestene dine skal gå i filer med «_test.go» -seksemplet. Du kan dele testene dine over flere filer som følger denne konvensjonen. For eksempel: "whatever1_test.go" og "whatever2_test.go". Du bør sette testfunksjonene i disse testfilene.
Hver testfunksjon er en offentlig eksportert funksjon hvis navn starter med "Test", aksepterer en peker til a testing.T
objekt, og returnerer ingenting. Det ser ut som:
func TestWhatever (t * testing.T) // Din testkode går her
T-objektet gir forskjellige metoder du kan bruke til å indikere feil eller opptaksfeil.
Husk: Bare testfunksjoner definert i testfiler vil bli utført av gå test
kommando.
Hver test følger samme strøm: Konfigurer testmiljøet (valgfritt), mat inn koden under testinngang, fang opp resultatet og sammenlign det med forventet utgang. Legg merke til at innganger og resultater ikke behøver å være argumenter for en funksjon.
Hvis koden som testes, henter data fra en database, vil innspillingen være sikker på at databasen inneholder passende testdata (som kan innebære å mocking på ulike nivåer). Men for vår søknad er det vanlige scenariet om å sende inngangsargumenter til en funksjon og å sammenligne resultatet til funksjonsutgangen tilstrekkelig.
La oss begynne med SumList ()
funksjon. Denne funksjonen tar et tall av heltall og returnerer summen deres. Her er en testfunksjon som bekrefter SumList ()
oppfører seg som det skal.
Den tester to testtilfeller, og hvis en forventet utgang ikke samsvarer med resultatet, kalles den Feil()
metode for testing.T objekt.
func TestSumList_NotIdiomatic (t * testing.T) // Test [] -> 0 resultat: = SumList ([] int ) hvis resultat! = 0 t.Error ("For input:", [] int , "forventet:", 0, "fikk:", resultat) // Test [] 4, 8, 9 -> 21 result = SumList ([] int 4, 8, 9) ! = 21 t.Error ("For inngang:", [] int , "forventet:", 0, "fikk:", resultat)
Dette er helt greit, men det ser litt utropt ut. Idiomatic Go testing bruker tabell-drevne tester hvor du definerer en struktur for par av innganger og forventede utganger, og deretter har du en liste over disse parene som du mater i en loop til samme logikk. Slik er det gjort for å teste SumList ()
funksjon.
type List2IntTestPair struct input] int funk TestSumList (t * testing.T) var tester = [] List2IntTestPair [] int , 0, [] int 1, 1, [] int 1, 2, 3, [] int 12, 13, 25, 7, 57, for _, par: = range tester resultat: = SumList (pair.input) ! = pair.output t.Error ("For input:", pair.input, "expected:", pair.output, "got:", result)
Dette er mye bedre. Det er enkelt å legge til flere testtilfeller. Det er enkelt å ha hele spekteret av testtilfeller på ett sted, og hvis du bestemmer deg for å endre testlogikken, trenger du ikke å endre flere forekomster.
Her er et annet eksempel for testing av SquareList ()
funksjon. I dette tilfellet er både inngang og utgang skiver av heltall, så testparstrukturen er forskjellig, men strømmen er identisk. En interessant ting her er at Go ikke gir en innebygd måte å sammenligne stykker, så jeg bruker reflect.DeepEqual ()
å sammenligne utgangssnittet til det forventede stykket.
type List2ListTestPair struktur input [] int utgang [] int func TestSquareList (t * testing.T) var tester = [] List2ListTestPair [] int , [] int , [] int 1 , [] int 1, [] int 2, [] int 4, [] int 3, 5, 7, [] int 9, 25, 49, for _, pair: = range tester result: = SquareList (pair.input) hvis! reflektere.DeepEqual (result, pair.output) t.Error ("For input:", pair.input, "expected:" , pair.output, "got:", result)
Running tester er like enkelt som å skrive gå test
i pakken din. Gå vil finne alle filene med "_test.go" suffiks og alle funksjonene med "Test" prefikset og kjøre dem som tester. Slik ser det ut når alt er i orden:
(G) / prosjekt-euler / 6 / go> gå test PASS ok _ / Brukere / gigi / Dokumenter / dev / github / prosjekt-euler / 6 / go 0.006s
Ikke veldig dramatisk. La meg slå en test med vilje. Jeg endrer test saken for SumList ()
slik at forventet utgang for summering 1 og 2 vil være 7.
funk TestSumList (t * testing.T) var tester = [] List2IntTestPair [] int , 0, [] int 1, 1, [] int 1, 2, 7 , int] 12, 13, 25, 7, 57 for _, par: = rekkeprøver resultat: = SumList (pair.input) hvis resultat! = pair.output t.Error For input: ", pair.input," forventet: ", pair.output," got: ", result)
Nå, når du skriver inn gå test
, du får:
(G) / prosjekt-euler / 6 / go> gå test --- FAIL: TestSumList (0.00s) 006_sum_square_difference_test.go: 80: For inngang: [1 2] forventet: 7 fikk: 3 FAIL avslutt status 1 Feil _ / Brukere / Gigi / Dokumenter / dev / github / prosjekt-euler / 6 / go 0.006s
Det sier ganske godt hva som skjedde og burde gi deg all den informasjonen du trenger for å fikse problemet. I dette tilfellet er problemet at testen selv er feil, og den forventede verdien skal være 3. Det er en viktig leksjon. Ikke automatisk anta at hvis en test mislykkes, er koden under test brutt. Vurder hele systemet, som inkluderer koden under testen, selve testen og testmiljøet.
For å sikre at koden fungerer, er det ikke nok å ha bestått test. Et annet viktig aspekt er testdekning. Tester dine tester alle setninger i koden? Noen ganger er det ikke nok. Hvis du for eksempel har en sløyfe i koden din som kjører til en tilstand er oppfylt, kan du teste det med en tilstand som virker, men unnlater å legge merke til at i noen tilfeller kan tilstanden alltid være feil, noe som resulterer i en uendelig sløyfe.
Enhetstester er som å pusse tennene og tetningen. Du bør ikke overse dem. De er den første barrieren mot problemer, og lar deg få tillit til refactoring. De er også en velsignelse når du prøver å reprodusere problemer og være i stand til å skrive en feiltest som demonstrerer problemet som går etter at du har løst problemet.
Integrasjonstester er også nødvendige. Tenk på dem som å besøke tannlegen. Du kan være OK uten dem for en stund, men hvis du forsømmer dem for lenge, blir det ikke pent.
De fleste ikke-trivielle programmer er laget av flere sammenhengende moduler eller komponenter. Det kan ofte oppstå problemer når man kobler disse komponentene sammen. Integrasjonstester gir deg tillit til at hele systemet fungerer som beregnet. Det finnes mange andre typer tester som aksepttest, ytelsestester, stress / belastningstest og fullverdige hele systemtester, men enhetstester og integreringstester er to av grunnleggende måter å teste programvare på..
Go har innebygd støtte for testing, en veldefinert måte å skrive tester på, og anbefalte retningslinjer i form av tabelldrevne tester.
Behovet for å skrive spesielle strukturer for hver kombinasjon av innganger og utdata er litt irriterende, men det er prisen du betaler for Go er enkel ved designtilnærming.