In diesem Tutorial werde ich Ihnen alle Grundlagen des idiomatischen Testens in Go anhand der von den Sprachdesignern und der Community entwickelten Best Practices vermitteln. Die Hauptwaffe wird das Standardtestpaket sein. Das Ziel ist ein Beispielprogramm, das ein einfaches Problem von Project Euler löst.
Go ist eine unglaublich mächtige Programmiersprache. Sie lernen alles vom Schreiben einfacher Hilfsprogramme bis zum Erstellen von skalierbaren, flexiblen Webservern in unserem gesamten Kurs.
Das Problem der Summenquadratdifferenz ist ziemlich einfach: "Finden Sie die Differenz zwischen der Summe der Quadrate der ersten einhundert natürlichen Zahlen und dem Quadrat der Summe."
Dieses spezielle Problem kann recht knapp gelöst werden, insbesondere wenn Sie Ihre Gauß kennen. Zum Beispiel ist die Summe der ersten N natürlichen Zahlen (1 + N) * N / 2
, und die Summe der Quadrate der ersten N ganzen Zahlen ist: (1 + N) * (N * 2 + 1) * N / 6
. Das ganze Problem kann also durch die folgende Formel gelöst werden und N zu 100 zugewiesen werden:
(1 + N) * (N * 2 + 1) * N / 6 - ((1 + N) * N / 2) * ((1 + N) * N / 2)
Nun, das ist sehr spezifisch und es gibt nicht viel zu testen. Stattdessen habe ich einige Funktionen erstellt, die etwas allgemeiner sind, als für dieses Problem erforderlich ist, aber in der Zukunft für andere Programme verwendet werden kann (Projekt Euler hat derzeit 559 Probleme)..
Der Code ist auf GitHub verfügbar.
Hier sind die Signaturen der vier Funktionen:
// Die MakeIntList () - Funktion gibt ein Array von aufeinanderfolgenden Ganzzahlen zurück //, beginnend von 1 bis zur 'number' (einschließlich der Anzahl). Func MakeIntList (number int) [] int // Die Funktion squareList () nimmt einen Schnitt von Ganzzahlen und gibt ein // Array der Quares dieser Ganzzahlen zurück func SquareList (numbers [] int) [] int // Die Funktion sumList () verwendet ein Segment von Ganzzahlen und gibt ihre Summenfunktion auf SumList (numbers [] int) int zurück // Solve Project Euler # 6 - Summe der quadratischen Unterschiede func Prozess (Anzahl int) int
Nun, mit unserem Zielprogramm (bitte verzeihen Sie mir, TDD-Eiferer), wollen wir sehen, wie man Tests für dieses Programm schreibt.
Das Testpaket geht Hand in Hand mit dem test gehen
Befehl. Ihre Pakettests sollten in Dateien mit dem Suffix "_test.go" gespeichert werden. Sie können Ihre Tests auf mehrere Dateien aufteilen, die dieser Konvention folgen. Zum Beispiel: "whatever1_test.go" und "whatever2_test.go". Sie sollten Ihre Testfunktionen in diese Testdateien einfügen.
Jede Testfunktion ist eine öffentlich exportierte Funktion, deren Name mit "Test" beginnt und einen Zeiger auf a akzeptiert Testen.T
Objekt und gibt nichts zurück. Es sieht aus wie:
func TestWhatever (t * testing.T) // Ihr Testcode geht hier hin
Das T-Objekt stellt verschiedene Methoden zur Verfügung, mit denen Sie Fehler oder Aufzeichnungsfehler anzeigen können.
Denken Sie daran: Nur in den Testdateien definierte Testfunktionen werden von der ausgeführt test gehen
Befehl.
Jeder Test folgt dem gleichen Ablauf: Richten Sie die Testumgebung ein (optional), geben Sie den Code unter Testeingabe ein, erfassen Sie das Ergebnis und vergleichen Sie es mit der erwarteten Ausgabe. Beachten Sie, dass Eingaben und Ergebnisse keine Argumente für eine Funktion sein müssen.
Wenn der zu testende Code Daten aus einer Datenbank abruft, stellt die Eingabe sicher, dass die Datenbank geeignete Testdaten enthält (was auf verschiedenen Ebenen Spott machen kann). Für unsere Anwendung ist das übliche Szenario jedoch ausreichend, Eingabeargumente an eine Funktion zu übergeben und das Ergebnis mit der Funktionsausgabe zu vergleichen.
Beginnen wir mit dem SumList ()
Funktion. Diese Funktion nimmt eine Scheibe ganzer Zahlen und gibt deren Summe zurück. Hier ist eine Testfunktion, die überprüft SumList ()
verhält sich wie es sollte.
Es testet zwei Testfälle und wenn eine erwartete Ausgabe nicht mit dem Ergebnis übereinstimmt, ruft sie das auf Error()
Methode des testing.T-Objekts.
func TestSumList_NotIdiomatic (t * testing.T) // Test [] -> 0 result: = SumList ([] int ) if result! = 0 t.Error ("Für Eingabe:", [] int , "erwartet:", 0, "bekam:", Ergebnis) // Test [] 4, 8, 9 -> 21 result = SumList ([] int 4, 8, 9) if result ! = 21 t.Fehler ("Für die Eingabe:", [] int , "erwartet:", 0, "erhalten:", Ergebnis)
Das ist alles unkompliziert, aber es sieht ein wenig wortreich aus. Idiomatic Go-Tests verwenden tabellengesteuerte Tests, bei denen Sie eine Struktur für Eingangspaare und erwartete Ausgänge definieren und dann eine Liste dieser Paare erhalten, die Sie in einer Schleife derselben Logik zuführen. So testen Sie das SumList ()
Funktion.
type List2IntTestPair struct input [] int ausgabe int func TestSumList (t * testing.T) var tests = [] List2IntTestPair [] int , 0, [] int 1, 1, [] int 1, 2, 3, [] int 12, 13, 25, 7, 57, für _, pair: = Bereichstests result: = SumList (pair.input), wenn result ! = pair.output t.Error ("Für die Eingabe:", pair.input, "erwartet:", pair.output, "got:", Ergebnis)
Das ist viel besser. Es ist einfach, weitere Testfälle hinzuzufügen. Es ist einfach, das gesamte Spektrum der Testfälle an einem Ort zu haben, und wenn Sie die Testlogik ändern möchten, müssen Sie nicht mehrere Instanzen ändern.
Hier ist ein weiteres Beispiel zum Testen der Quadratische Liste ()
Funktion. In diesem Fall sind sowohl die Eingabe als auch die Ausgabe Slices von Ganzzahlen. Die Struktur des Testpaares ist unterschiedlich, der Fluss ist jedoch identisch. Eine interessante Sache hier ist, dass Go keine eingebaute Möglichkeit zum Vergleichen von Slices bietet reflect.DeepEqual ()
um den Ausgabeschnitt mit dem erwarteten Schnitt zu vergleichen.
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, für _, pair: = Bereichstests result: = SquareList (pair.input) if! reflect.DeepEqual (result, pair.output) t.Error ("Für Eingabe:", pair.input, "erwartet:" , pair.output, "got:", Ergebnis)
Das Ausführen von Tests ist so einfach wie das Tippen test gehen
in Ihrem Paketverzeichnis. Go findet alle Dateien mit dem Suffix "_test.go" und alle Funktionen mit dem Präfix "Test" und führt sie als Tests aus. So sieht es aus, wenn alles in Ordnung ist:
(G) / project-euler / 6 / go> go test PASS ok _ / Benutzer / gigi / Documents / dev / github / project-euler / 6 / go 0,006s
Nicht sehr dramatisch Lassen Sie mich absichtlich einen Test brechen. Ich werde den Testfall für ändern SumList ()
so dass die erwartete Ausgabe für das Summieren von 1 und 2 7 ist.
func TestSumList (t * testing.T) var tests = [] List2IntTestPair [] int , 0, [] int 1, 1, [] int 1, 2, 7 , [] int 12, 13, 25, 7, 57, für _, pair: = Bereichstests result: = SumList (pair.input) if result! = pair.output t.Error (" Für die Eingabe: ", pair.input," erwartet: ", pair.output," got: ", Ergebnis)
Nun, wenn Sie tippen test gehen
, du kriegst:
(G) / project-euler / 6 / go> go test --- FAIL: TestSumList (0,00s) 006_sum_square_difference_test.go: 80: Für Eingabe: [1 2] erwartet: 7 erhalten: 3 FAIL-Exitstatus 1 FAIL _ / Benutzer / Gigi / Dokumente / dev / github / project-euler / 6 / go 0.006s
Das sagt ziemlich gut aus, was passiert ist und sollte Ihnen alle Informationen geben, die Sie benötigen, um das Problem zu beheben. In diesem Fall besteht das Problem darin, dass der Test selbst falsch ist und der erwartete Wert 3 sein sollte. Dies ist eine wichtige Lektion. Gehen Sie nicht automatisch davon aus, dass der zu testende Code fehlerhaft ist, wenn ein Test fehlschlägt. Betrachten Sie das gesamte System, das den getesteten Code, den Test selbst und die Testumgebung umfasst.
Um sicherzustellen, dass Ihr Code funktioniert, reicht es nicht aus, bestandene Tests zu haben. Ein weiterer wichtiger Aspekt ist die Testabdeckung. Umfassen Ihre Tests jede Aussage im Code? Manchmal reicht auch das nicht aus. Wenn sich in Ihrem Code beispielsweise eine Schleife befindet, die ausgeführt wird, bis eine Bedingung erfüllt ist, können Sie sie mit einer funktionierenden Bedingung erfolgreich testen. In manchen Fällen wird jedoch nicht erkannt, dass die Bedingung immer falsch ist, was zu einer Endlosschleife führt.
Unit-Tests sind wie Zähneputzen und Zahnseide. Sie sollten sie nicht vernachlässigen. Sie sind die erste Barriere gegen Probleme und geben Ihnen Vertrauen in das Refactoring. Sie sind auch ein Segen, wenn Sie versuchen, Probleme zu reproduzieren und in der Lage sind, einen fehlgeschlagenen Test zu schreiben, der das Problem zeigt, das nach Behebung des Problems besteht.
Integrationstests sind ebenfalls notwendig. Betrachten Sie sie als Besuch beim Zahnarzt. Sie sind vielleicht für eine Weile ohne sie in Ordnung, aber wenn Sie sie zu lange vernachlässigen, wird das nicht schön sein.
Die meisten nicht trivialen Programme bestehen aus mehreren miteinander verbundenen Modulen oder Komponenten. Probleme können häufig auftreten, wenn diese Komponenten miteinander verbunden werden. Integrationstests geben Ihnen die Sicherheit, dass Ihr gesamtes System wie beabsichtigt funktioniert. Es gibt viele andere Arten von Tests, z. B. Abnahmetests, Leistungstests, Belastungs- / Belastungstests und vollständige Systemtests, aber Unit-Tests und Integrationstests sind zwei der grundlegenden Möglichkeiten, Software zu testen.
Go bietet integrierte Unterstützung für Tests, eine gut definierte Methode zum Schreiben von Tests und empfohlene Richtlinien in Form von tabellengesteuerten Tests.
Die Notwendigkeit, spezielle Strukturen für jede Kombination von Ein- und Ausgängen zu schreiben, ist etwas ärgerlich, aber dies ist der Preis, den Sie für Gos einfachen Ansatz zahlen.