I denna handledning lär jag dig alla grunderna för idiomatisk provning i Go med hjälp av de bästa metoderna som utvecklats av språkdesignerna och samhället. Huvudvapnet är standardtestpaketet. Målet är ett provprogram som löser ett enkelt problem från Project Euler.
Go är ett otroligt kraftfullt programmeringsspråk, lära allt från att skriva enkla verktyg för att bygga skalbara, flexibla webbservrar i hela vår kurs.
Summa kvadratskillnadsproblemet är ganska enkelt: "Hitta skillnaden mellan summan av kvadraterna för de första hundra naturliga talen och kvadraten av summan."
Det här problemet kan lösas ganska kortfattat, särskilt om du känner till din Gauss. Till exempel är summan av de första N naturliga talen (1 + N) * N / 2
, och summan av kvadraterna för de första N heltal är: (1 + N) * (N * 2 + 1) * N / 6
. Så hela problemet kan lösas med följande formel och tilldela 100 till N:
(1 + N) * (N * 2 + 1) * N / 6 - ((1 + N) * N / 2) * ((1 + N) * N / 2)
Jo, det är väldigt specifikt, och det är inte mycket att testa. Istället skapade jag några funktioner som är lite mer allmänna än vad som behövs för detta problem, men kan tjäna för andra program i framtiden (projektet Euler har 559 problem just nu).
Koden är tillgänglig på GitHub.
Här är signaturerna för de fyra funktionerna:
// MakeIntList () -funktionen returnerar en rad sammanhängande heltal // börjar från 1 hela vägen till "talet" (inklusive numret) func MakeIntList (nummer int) [] int // Funktionen squareList () tar en skiva av heltal och returnerar en // array av kvadraterna av dessa heltal func SquareList (siffror [] int) [] int // SumList () -funktionen tar en rad heltal och returnerar summan func SumList (siffror [] int) int // Lösa projekt Euler # 6 - Summa kvadratdifferens func Process (antal int) int
Nu, med vårt målprogram på plats (tack förlåt mig, TDD-fanatiker), låt oss se hur man skriver tester för detta program.
Testpaketet går hand i hand med gå test
kommando. Din pakettest ska gå i filer med suffixet _test.go. Du kan dela dina test över flera filer som följer denna konvention. Till exempel: "whatever1_test.go" och "whatever2_test.go". Du bör lägga dina testfunktioner i dessa testfiler.
Varje testfunktion är en offentligt exporterad funktion vars namn börjar med "Test", accepterar en pekare till a testing.T
objekt och returnerar ingenting. Det ser ut som:
func TestWhatever (t * test.T) // Din testkod går här
T-objektet tillhandahåller olika metoder som du kan använda för att indikera fel eller inspelningsfel.
Kom ihåg att endast testfunktioner som definieras i testfiler kommer att utföras av gå test
kommando.
Varje test följer samma flöde: Ställ in testmiljön (tillval), mata in koden under testingången, fånga resultatet och jämför det med den förväntade utmatningen. Observera att inmatningar och resultat inte behöver vara argument för en funktion.
Om koden som testas hämtar data från en databas så kommer inmatningen att se till att databasen innehåller lämpliga testdata (vilket kan innebära att man stöter på olika nivåer). Men för vår ansökan är det vanliga scenariot att överföra inmatningsargument till en funktion och jämföra resultatet med funktionsutgången tillräcklig.
Låt oss börja med SumList ()
fungera. Den här funktionen tar ett segment av heltal och returnerar summan. Här är en testfunktion som verifierar SumList ()
beter sig som det borde.
Det testar två testfall, och om en förväntad utgång inte matchar resultatet, kallas det Fel()
metod för testet.T objekt.
func TestSumList_NotIdiomatic (t * test.T) // Test [] -> 0 resultat: = SumList ([] int ) om resultatet! = 0 t.Error ("För ingång:", [] int , "förväntat:", 0, "fick:", resultat) // Test [] 4, 8, 9 -> 21 result = SumList ([] int 4, 8, 9) om resultatet ! = 21 t.Error ("För inmatning:", [] int , "förväntat:", 0, "fick:", resultat)
Det här är helt enkelt, men det ser lite ordentligt ut. Idiomatic Go test använder tabelldrivna tester där du definierar en struktur för par av ingångar och förväntade utgångar och sedan har en lista över dessa par som du matar in en loop till samma logik. Så här är det gjort för att testa SumList ()
fungera.
skriv List2IntTestPair struktur input] int , 0, [] int 1, 1, [] int 1, 2, 3, [] int 12, 13, 25, 7, 57, för _, par: = intervalltest resultat: = SumList (par.input) ! = pair.output t.Error ("För input:", pair.input, "expected:", pair.output, "got:", result)
Detta är mycket bättre. Det är lätt att lägga till fler testfall. Det är lätt att ha hela spektret av testfall på ett ställe, och om du väljer att ändra testlogiken behöver du inte ändra flera instanser.
Här är ett annat exempel för att testa SquareList ()
fungera. I detta fall är både ingången och utgången skivor av heltal, så testparstrukturen är annorlunda, men flödet är identiskt. En intressant sak här är att Go inte ger ett inbyggt sätt att jämföra skivor, så jag använder reflect.DeepEqual ()
för att jämföra utmatningsskivan med den förväntade skivan.
skriv List2ListTestPair struct input [output] [] int func TestSquareList (t * test.T) var test = [] List2ListTestPair [] int , [] int , [] int 1 , [] int 1, [] int 2, [] int 4, [] int 3, 5, 7, [] int 9, 25, 49 för _, par: = intervalltest resultat: = SquareList (pair.input) om! reflektera.DeepEqual (result, pair.output) t.Error ("För input:", pair.input, "expected:" , pair.output, "got:", result)
Running tester är lika enkelt som att skriva gå test
i din paketkatalog. Go hittar alla filer med "_test.go" suffixet och alla funktioner med "Test" prefixet och kör dem som test. Så här ser det ut när allt är okej:
(G) / project-euler / 6 / go> gå test PASS ok _ / Användare / gigi / Documents / dev / github / project-euler / 6 / go 0.006s
Inte särskilt dramatisk. Låt mig bryta ett test med syfte. Jag ska ändra testfallet för SumList ()
så att den förväntade utgången för summering 1 och 2 blir 7.
func TestSumList (t * test.T) var test = [] List2IntTestPair [] int , 0, [] int 1, 1, [] int 1, 2, 7 , [] int 12, 13, 25, 7, 57, för _, par: = intervalltest resultat: = SumList (par.input) om resultat! = pair.output t.Error För input: ", pair.input," expected: ", pair.output," got: ", result)
Nu när du skriver gå test
, du får:
(G) / project-euler / 6 / go> gå test --- FEL: TestSumList (0.00s) 006_sum_square_difference_test.go: 80: För inmatning: [1 2] förväntat: 7 fick: 3 FEL utgångsstatus 1 FEL _ / Användare / gigi / Dokument / dev / github / project-euler / 6 / go 0.006s
Det säger ganska bra vad som hände och borde ge dig all information du behöver för att åtgärda problemet. I det här fallet är problemet att testet själv är fel och det förväntade värdet ska vara 3. Det är en viktig lektion. Antag inte automatiskt att om ett test misslyckas är koden under test bruten. Tänk på hela systemet, vilket inkluderar koden under testet, själva testet och testmiljön.
För att säkerställa att din kod fungerar, räcker det inte med att klara prov. En annan viktig aspekt är provtäckning. Täcker dina tester varje uttalande i koden? Ibland är det inte tillräckligt. Om du till exempel har en slinga i koden som körs tills ett villkor är uppfyllt kan du testa det med ett tillstånd som fungerar men märker inte att villkoret i vissa fall alltid kan vara falskt, vilket resulterar i en oändlig slinga.
Enhetstest är som att borsta tänderna och tandtråd. Du bör inte försumma dem. De är den första barriären mot problem och låter dig få förtroende för refactoring. De är också en välsignelse när man försöker reproducera problem och kunna skriva ett misslyckat test som visar problemet som passerar efter att du åtgärdat problemet.
Integrationstester är också nödvändiga. Tänk på dem som att besöka tandläkaren. Du kan vara OK utan dem för en stund, men om du försummar dem för länge blir det inte söt.
De flesta icke-triviala program är gjorda av flera interrelaterade moduler eller komponenter. Problem kan ofta uppstå när man kopplar dessa komponenter ihop. Integrationstest ger dig förtroende för att hela ditt system fungerar som avsett. Det finns många andra typer av tester som acceptanstest, prestanda tester, stress / belastningstest och fullfjädrad hela systemtest, men enhetstester och integrationsprov är två av de grundläggande sätten att testa programvara.
Go har inbyggt stöd för testning, ett väldefinierat sätt att skriva test och rekommenderade riktlinjer i form av tabellstyrda test.
Behovet av att skriva speciella strukturer för varje kombination av ingångar och utgångar är lite irriterande, men det är priset du betalar för Go är enkelt genom designmetoden.