Heim > Artikel > Backend-Entwicklung > Von PHPUnit zu Go: Datengesteuertes Unit-Testen für Go-Entwickler
In diesem Beitrag untersuchen wir, wie wir die PHP-Unit-Testing-Denkweise, insbesondere den Datenanbieteransatz des PHPUnit-Frameworks, in Go integrieren können. Wenn Sie ein erfahrener PHP-Entwickler sind, sind Sie wahrscheinlich mit dem Datenanbietermodell vertraut: Testdaten separat in Roharrays sammeln und diese Daten in eine Testfunktion einspeisen. Dieser Ansatz macht Unit-Tests sauberer, wartbarer und folgt Prinzipien wie Offen/Geschlossen.
Die Verwendung eines Datenanbieter-Ansatzes zur Strukturierung von Unit-Tests in Go bietet mehrere Vorteile, darunter:
Verbesserte Lesbarkeit und Erweiterbarkeit: Tests werden visuell organisiert, wobei oben klar getrennte Arrays jedes Testszenario darstellen. Der Schlüssel jedes Arrays beschreibt das Szenario, während sein Inhalt die Daten zum Testen dieses Szenarios enthält. Durch diese Struktur ist die Datei angenehm zu bearbeiten und leicht zu erweitern.
Trennung von Belangen: Das Datenanbietermodell hält Daten und Testlogik getrennt, was zu einer leichten, entkoppelten Funktion führt, die im Laufe der Zeit weitgehend unverändert bleiben kann. Das Hinzufügen eines neuen Szenarios erfordert lediglich das Anhängen weiterer Daten an den Anbieter, wodurch die Testfunktion für Erweiterungen offen, aber für Änderungen geschlossen bleibt – eine praktische Anwendung des Offen/Geschlossen-Prinzips beim Testen.
In einigen Projekten habe ich sogar Szenarien gesehen, die so dicht waren, dass sie die Verwendung einer separaten JSON-Datei als Datenquelle rechtfertigten, die manuell erstellt und an den Anbieter weitergeleitet wurde, der wiederum Daten an die Testfunktion liefert.
Die Verwendung von Datenanbietern empfiehlt sich besonders, wenn Sie über eine große Anzahl von Testfällen mit unterschiedlichen Daten verfügen: Jeder Testfall ist konzeptionell ähnlich, unterscheidet sich jedoch nur in der Eingabe und der erwarteten Ausgabe.
Die Vermischung von Daten und Logik in einer einzigen Testfunktion kann die Developer Experience (DX) verringern. Es führt oft zu:
Verbosity Overload: Redundanter Code, der Anweisungen mit geringfügigen Datenvariationen wiederholt, was zu einer Codebasis führt, die ohne zusätzlichen Nutzen ausführlich ist.
Reduzierte Klarheit: Das Durchsuchen der Testfunktion wird zu einer lästigen Pflicht, wenn versucht wird, die tatsächlichen Testdaten vom umgebenden Code zu isolieren, was der Datenanbieter-Ansatz natürlich erleichtert.
Das DataProvider-Muster in PHPUnit, bei dem im Grunde die Provider-Funktion die Testfunktion mit verschiedenen Datensätzen versorgt, die in einer impliziten Schleife verarbeitet werden. Es gewährleistet das DRY-Prinzip (Don't Repeat Yourself) und stimmt auch mit dem Open/Closed-Prinzip überein, indem es das Hinzufügen oder Ändern von Testszenarien erleichtert, ohne die Kernlogik der Testfunktion zu ändern.
Um die Nachteile von Ausführlichkeit, Codeduplizierung und Wartungsherausforderungen zu veranschaulichen, finden Sie hier einen Ausschnitt eines Beispiels für einen Komponententest für die Blasensortierungsfunktion ohne die Hilfe von Datenanbietern:
<?php declare(strict_types=1); use PHPUnit\Framework\TestCase; final class BubbleSortTest extends TestCase { public function testBubbleSortEmptyArray() { $this->assertSame([], BubbleSort([])); } public function testBubbleSortOneElement() { $this->assertSame([0], BubbleSort([0])); } public function testBubbleSortTwoElementsSorted() { $this->assertSame([5, 144], BubbleSort([5, 144])); } public function testBubbleSortTwoElementsUnsorted() { $this->assertSame([-7, 10], BubbleSort([10, -7])); } public function testBubbleSortMultipleElements() { $this->assertSame([1, 2, 3, 4], BubbleSort([1, 3, 4, 2])); } // And so on for each test case, could be 30 cases for example. public function testBubbleSortDescendingOrder() { $this->assertSame([1, 2, 3, 4, 5], BubbleSort([5, 4, 3, 2, 1])); } public function testBubbleSortBoundaryValues() { $this->assertSame([-2147483647, 2147483648], BubbleSort([2147483648, -2147483647])); } }
Gibt es Probleme mit dem oben genannten Code? sicher:
Ausführlichkeit: Jeder Testfall erfordert eine separate Methode, was zu einer großen, sich wiederholenden Codebasis führt.
Duplikation: Die Testlogik wird in jeder Methode wiederholt und variiert nur je nach Eingabe und erwarteter Ausgabe.
Offener/geschlossener Verstoß: Das Hinzufügen neuer Testfälle erfordert eine Änderung der Testklassenstruktur durch die Erstellung weiterer Methoden.
Hier ist dieselbe Testsuite umgestaltet, um einen Datenanbieter zu verwenden
<?php declare(strict_types=1); use PHPUnit\Framework\TestCase; final class BubbleSortTest extends TestCase { /** * Provides test data for bubble sort algorithm. * * @return array<string, array> */ public function bubbleSortDataProvider(): array { return [ 'empty' => [[], []], 'oneElement' => [[0], [0]], 'twoElementsSorted' => [[5, 144], [5, 144]], 'twoElementsUnsorted' => [[10, -7], [-7, 10]], 'moreThanOneElement' => [[1, 3, 4, 2], [1, 2, 3, 4]], 'moreThanOneElementWithRepetition' => [[1, 4, 4, 2], [1, 2, 4, 4]], 'moreThanOneElement2' => [[7, 7, 1, 0, 99, -5, 10], [-5, 0, 1, 7, 7, 10, 99]], 'sameElement' => [[1, 1, 1, 1], [1, 1, 1, 1]], 'negativeNumbers' => [[-5, -2, -10, -1, -3], [-10, -5, -3, -2, -1]], 'descendingOrder' => [[5, 4, 3, 2, 1], [1, 2, 3, 4, 5]], 'randomOrder' => [[9, 2, 7, 4, 1, 6, 3, 8, 5], [1, 2, 3, 4, 5, 6, 7, 8, 9]], 'duplicateElements' => [[2, 2, 1, 1, 3, 3, 4, 4], [1, 1, 2, 2, 3, 3, 4, 4]], 'largeArray' => [[-1, -10000, -12345, -2032, -23, 0, 0, 0, 0, 10, 10000, 1024, 1024354, 155, 174, 1955, 2, 255, 3, 322, 4741, 96524], [-1, -10000, -12345, -2032, -23, 0, 0, 0, 0, 10, 10000, 1024, 1024354, 155, 174, 1955, 2, 255, 3, 322, 4741, 96524]], 'singleNegativeElement' => [[-7], [-7]], 'arrayWithZeroes' => [[0, -2, 0, 3, 0], [-2, 0, 0, 0, 3]], 'ascendingOrder' => [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5]], 'descendingOrderWithDuplicates' => [[5, 5, 4, 3, 3, 2, 1], [1, 2, 3, 3, 4, 5, 5]], 'boundaryValues' => [[2147483648, -2147483647], [-2147483647, 2147483648]], 'mixedSignNumbers' => [[-1, 0, 1, -2, 2], [-2, -1, 0, 1, 2]], ]; } /** * @dataProvider bubbleSortDataProvider * * @param array<int> $input * @param array<int> $expected */ public function testBubbleSort(array $input, array $expected) { $this->assertSame($expected, BubbleSort($input)); } }
Gibt es Vorteile bei der Nutzung des Datenanbieters? ach ja:
Prägnanz: Alle Testdaten werden in einer einzigen Methode zentralisiert, sodass für jedes Szenario keine mehreren Funktionen erforderlich sind.
Verbesserte Lesbarkeit: Jeder Testfall ist gut organisiert, mit beschreibenden Schlüsseln für jedes Szenario.
Offenes/Geschlossenes Prinzip: Neue Fälle können zum Datenanbieter hinzugefügt werden, ohne die Kerntestlogik zu ändern.
Verbesserte DX (Entwicklererfahrung): Die Teststruktur ist sauber, optisch ansprechend und motiviert selbst faule Entwickler, sie zu erweitern, zu debuggen oder zu aktualisieren.
package sort import ( "testing" "github.com/stretchr/testify/assert" ) type TestData struct { ArrayList map[string][]int ExpectedList map[string][]int } const ( maxInt32 = int32(^uint32(0) >> 1) minInt32 = -maxInt32 - 1 ) var testData = &TestData{ ArrayList: map[string][]int{ "empty": {}, "oneElement": {0}, "twoElementsSorted": {5, 144}, "twoElementsUnsorted": {10, -7}, "moreThanOneElement": {1, 3, 4, 2}, "moreThanOneElementWithRepetition": {1, 4, 4, 2}, "moreThanOneElement2": {7, 7, 1, 0, 99, -5, 10}, "sameElement": {1, 1, 1, 1}, "negativeNumbers": {-5, -2, -10, -1, -3}, "descendingOrder": {5, 4, 3, 2, 1}, "randomOrder": {9, 2, 7, 4, 1, 6, 3, 8, 5}, "duplicateElements": {2, 2, 1, 1, 3, 3, 4, 4}, "largeArray": {-1, -10000, -12345, -2032, -23, 0, 0, 0, 0, 10, 10000, 1024, 1024354, 155, 174, 1955, 2, 255, 3, 322, 4741, 96524}, "singleNegativeElement": {-7}, "arrayWithZeroes": {0, -2, 0, 3, 0}, "ascendingOrder": {1, 2, 3, 4, 5}, "descendingOrderWithDuplicates": {5, 5, 4, 3, 3, 2, 1}, "boundaryValues": {2147483648, -2147483647}, "mixedSignNumbers": {-1, 0, 1, -2, 2}, }, ExpectedList: map[string][]int{ "empty": {}, "oneElement": {0}, "twoElementsSorted": {5, 144}, "twoElementsUnsorted": {-7, 10}, "moreThanOneElement": {1, 2, 3, 4}, "moreThanOneElementWithRepetition": {1, 2, 4, 4}, "moreThanOneElement2": {-5, 0, 1, 7, 7, 10, 99}, "sameElement": {1, 1, 1, 1}, "negativeNumbers": {-10, -5, -3, -2, -1}, "descendingOrder": {1, 2, 3, 4, 5}, "randomOrder": {1, 2, 3, 4, 5, 6, 7, 8, 9}, "duplicateElements": {1, 1, 2, 2, 3, 3, 4, 4}, "largeArray": {-1, -10000, -12345, -2032, -23, 0, 0, 0, 0, 10, 10000, 1024, 1024354, 155, 174, 1955, 2, 255, 3, 322, 4741, 96524}, "singleNegativeElement": {-7}, "arrayWithZeroes": {-2, 0, 0, 0, 3}, "ascendingOrder": {1, 2, 3, 4, 5}, "descendingOrderWithDuplicates": {1, 2, 3, 3, 4, 5, 5}, "boundaryValues": {-2147483647, 2147483648}, "mixedSignNumbers": {-2, -1, 0, 1, 2}, }, } func TestBubble(t *testing.T) { for testCase, array := range testData.ArrayList { t.Run(testCase, func(t *testing.T) { actual := Bubble(array) assert.ElementsMatch(t, actual, testData.ExpectedList[testCase]) }) } }
Bonus: Ein Github-Repository, das die in diesem Blogpost vorgestellte Logik implementiert, finden Sie hier https://github.com/MedUnes/dsa-go. Bisher enthält es Github-Aktionen, die diese Tests durchführen und sogar das superberühmte grüne Abzeichen zeigen ;)
Wir sehen uns im nächsten [hoffentlich] informativen Beitrag!
Das obige ist der detaillierte Inhalt vonVon PHPUnit zu Go: Datengesteuertes Unit-Testen für Go-Entwickler. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!