Let's Go Golang-programma's testen

In deze tutorial zal ik je alle basisbeginselen van idiomatisch testen in Go leren met behulp van de best practices die zijn ontwikkeld door de taalontwerpers en de community. Het belangrijkste wapen zal het standaard testpakket zijn. Het doelwit is een voorbeeldprogramma dat een eenvoudig probleem van Project Euler oplost.

Go is een ongelooflijk krachtige programmeertaal, leer alles van het schrijven van eenvoudige hulpprogramma's tot het bouwen van schaalbare, flexibele webservers in onze volledige cursus.

Vierkant totaalverschil

Het probleem met het sum-vierkantverschil is vrij eenvoudig: "Zoek het verschil tussen de som van de vierkanten van de eerste honderd natuurlijke getallen en het kwadraat van de som." 

Dit specifieke probleem kan nogal bondig worden opgelost, vooral als je je Gauss kent. De som van de eerste N natuurlijke getallen is bijvoorbeeld (1 + N) * N / 2, en de som van vierkanten van de eerste N gehele getallen is: (1 + N) * (N * 2 + 1) * N / 6. Dus het hele probleem kan worden opgelost met de volgende formule en 100 toewijzen aan N:

(1 + N) * (N * 2 + 1) * N / 6 - ((1 + N) * N / 2) * ((1 + N) * N / 2)

Welnu, dat is heel specifiek en er valt niet veel te testen. In plaats daarvan heb ik een aantal functies gemaakt die iets algemener zijn dan wat nodig is voor dit probleem, maar die in de toekomst ook voor andere programma's kunnen dienen (project Euler heeft nu 559 problemen).

De code is beschikbaar op GitHub.

Hier zijn de handtekeningen van de vier functies:

// De functie MakeIntList () retourneert een reeks opeenvolgende gehele getallen // beginnend van 1 helemaal naar het 'getal' (inclusief het getal) func MakeIntList (getal int) [] int // De functie squareList () neemt een segment van gehele getallen en geeft een // array van de quares van deze gehele getallen terug func SquareList (getallen [] int) [] int // De functie sumList () neemt een segment van gehele getallen en retourneert hun som func SumList (getallen [] int) int // Los project Euler # 6 op - Som vierkante verschil func Proces (aantal int) int

Nu, met ons doelprogramma op zijn plaats (vergeef me, TDD-fanatici), laten we eens kijken hoe we tests voor dit programma kunnen schrijven.

Het testpakket

Het testpakket gaat hand in hand met de ga testen commando. Uw pakkettesten zouden in bestanden moeten gaan met het achtervoegsel "_test.go". U kunt uw tests verdelen over verschillende bestanden die deze conventie volgen. Bijvoorbeeld: "whatever1_test.go" en "whatever2_test.go". U moet uw testfuncties in deze testbestanden plaatsen.

Elke testfunctie is een publiek geëxporteerde functie waarvan de naam begint met "Test", accepteert een pointer naar een testing.T object en retourneert niets. Het lijkt op:

func Test Wat dan ook (t * testing.T) // Uw testcode komt hier 

Het T-object biedt verschillende methoden die u kunt gebruiken om fouten aan te geven of fouten op te nemen.

Let op: alleen testfuncties die zijn gedefinieerd in testbestanden worden uitgevoerd door de ga testen commando.

Tests schrijven

Elke test volgt dezelfde stroom: stel de testomgeving in (optioneel), voer de code onder de testinvoer in, leg het resultaat vast en vergelijk het met de verwachte uitvoer. Merk op dat invoer en resultaten geen argumenten hoeven te zijn voor een functie. 

Als de te testen code gegevens uit een database ophaalt, zorgt de invoer ervoor dat de database de juiste testgegevens bevat (waarbij mogelijk sprake is van spot op verschillende niveaus). Maar voor onze toepassing is het algemene scenario van het doorgeven van invoerargumenten aan een functie en het vergelijken van het resultaat met de functie-uitvoer voldoende.

Laten we beginnen met de SomLijst () functie. Deze functie neemt een segment van gehele getallen en geeft hun som terug. Hier is een testfunctie die verifieert SomLijst () gedraagt ​​zich zoals het hoort.

Het test twee testgevallen en als een verwachte uitvoer niet overeenkomt met het resultaat, wordt de Fout() methode van het testing.T object. 

func TestSumList_NotIdiomatic (t * testing.T) // Test []  -> 0 result: = SumList ([] int ) if result! = 0 t.Error ("For input:", [] int , "expected:", 0, "got:", result) // Test [] 4, 8, 9 -> 21 result = SumList ([] int 4, 8, 9) if result ! = 21 t.Error ("For input:", [] int , "expected:", 0, "got:", result)

Dit is allemaal eenvoudig, maar het ziet er een beetje uitgebreid uit. Idiomatic Go-tests maken gebruik van tabelgestuurde tests waarbij u een struct definieert voor paren ingangen en verwachte uitgangen en vervolgens een lijst hebt van deze paren die u in een lus naar dezelfde logica toevoert. Hier is hoe het wordt gedaan voor het testen van de SomLijst () functie.

type List2IntTestPair struct input [] int output int func TestSumList (t * testing.T) var tests = [] List2IntTestPair [] int , 0, [] int 1, 1,  [] int 1, 2, 3, [] int 12, 13, 25, 7, 57, for _, pair: = bereik testen result: = SumList (pair.input) if result ! = pair.output t.Error ("For input:", pair.input, "expected:", pair.output, "got:", result) 

Dit is veel beter. Het is gemakkelijk om meer testgevallen toe te voegen. Het is gemakkelijk om het volledige spectrum van testcases op één plaats te hebben en als u besluit de testlogica te wijzigen, hoeft u niet meerdere instanties te wijzigen.

Hier is nog een voorbeeld voor het testen van de SquareList () functie. In dit geval zijn zowel de invoer als de uitvoer plakken van gehele getallen, dus de structuur van het testpaar is anders, maar de stroom is identiek. Een interessant ding is dat Go geen ingebouwde manier biedt om segmenten te vergelijken, dus gebruik ik reflect.DeepEqual () om het uitvoersegment te vergelijken met het verwachte segment.

type List2ListTestPair struct input [] int output [] int func TestSquareList (t * testing.T) var tests = [] List2ListTestPair [] int , [] int , [] int 1 , [] int 1, [] int 2, [] int 4, [] int 3, 5, 7, [] int 9, 25, 49,  voor _, paar: = bereik testen resultaat: = SquareList (pair.input) if! reflect.DeepEqual (result, pair.output) t.Error ("For input:", pair.input, "expected:" , pair.output, "got:", result) 

Tests uitvoeren

Tests uitvoeren is net zo eenvoudig als typen ga testen in de map van uw pakket. Go vindt alle bestanden met het achtervoegsel "_test.go" en alle functies met het voorvoegsel "Test" en voert ze uit als tests. Hier is hoe het eruit ziet als alles in orde is:

(G) / project-euler / 6 / go> go-test PASS ok _ / Gebruikers / gigi / Documenten / dev / github / project-euler / 6 / go 0.006s

Niet erg dramatisch. Laat me een test expres breken. Ik zal de testcase wijzigen SomLijst () zodanig dat de verwachte output voor optelling 1 en 2 7 is.

func TestSumList (t * testing.T) var tests = [] List2IntTestPair [] int , 0, [] int 1, 1, [] int 1, 2, 7 , [] int 12, 13, 25, 7, 57, voor _, paar: = afstandstests result: = SumList (pair.input) if result! = pair.output t.Error (" Voor invoer: ", pair.input," expected: ", pair.output," got: ", result)

Nu, wanneer u typt ga testen, Jij krijgt:

(G) / project-euler / 6 / go> go-test --- FAIL: TestSumList (0.00s) 006_sum_square_difference_test.go: 80: Voor invoer: [1 2] verwacht: 7 gekregen: 3 FAIL exit-status 1 FAIL _ / Gebruikers / gigi / Documenten / dev / github / project-euler / 6 / go 0.006s

Dat verklaart vrij goed wat er is gebeurd en zou je alle informatie moeten geven die je nodig hebt om het probleem op te lossen. In dit geval is het probleem dat de test zelf fout is en de verwachte waarde 3 zou moeten zijn. Dat is een belangrijke les. Ga er niet automatisch van uit dat als een test mislukt de te testen code verbroken is. Overweeg het hele systeem, inclusief de te testen code, de test zelf en de testomgeving.

Test dekking

Om ervoor te zorgen dat uw code werkt, is het niet genoeg om tests te laten slagen. Een ander belangrijk aspect is de testdekking. Bevatten uw tests elke verklaring in de code? Soms is zelfs dat niet genoeg. Als u bijvoorbeeld een lus in uw code hebt die wordt uitgevoerd totdat aan een voorwaarde is voldaan, kunt u deze met succes testen met een voorwaarde die werkt, maar u merkt niet dat in sommige gevallen de voorwaarde altijd onwaar is, wat resulteert in een oneindige lus. 

Eenheidstests

Eenheidstests zijn als tandenpoetsen en flossen. Je moet ze niet negeren. Ze zijn de eerste barrière tegen problemen en laten je vertrouwen hebben in refactoring. Ze zijn ook een zegen bij het reproduceren van problemen en het kunnen schrijven van een mislukte test die het probleem laat zien dat overgaat nadat u het probleem hebt opgelost.

Integratietests

Integratietests zijn ook noodzakelijk. Zie ze als een bezoek aan de tandarts. Je bent misschien wel een tijdje zonder hen, maar als je ze te lang verwaarloost, zal het niet mooi zijn. 

De meeste niet-triviale programma's zijn gemaakt van meerdere onderling gerelateerde modules of componenten. Problemen kunnen vaak voorkomen bij het samen bedraden van die componenten. Integratietests geven je het vertrouwen dat je hele systeem werkt zoals bedoeld. Er zijn veel andere soorten tests, zoals acceptatietests, prestatietests, stress / belastingtests en volwaardige systeemtests, maar unit tests en integratietests zijn twee van de fundamentele manieren om software te testen.

Conclusie

Go heeft ingebouwde ondersteuning voor testen, een goed gedefinieerde manier om tests te schrijven en aanbevolen richtlijnen in de vorm van table-driven tests. 

De noodzaak om speciale structs te schrijven voor elke combinatie van inputs en outputs is een beetje vervelend, maar dat is de prijs die je betaalt voor Go's simple by design approach.