Heim  >  Artikel  >  Backend-Entwicklung  >  Von PHPUnit zu Go: Datengesteuertes Unit-Testen für Go-Entwickler

Von PHPUnit zu Go: Datengesteuertes Unit-Testen für Go-Entwickler

Patricia Arquette
Patricia ArquetteOriginal
2024-11-12 19:21:02197Durchsuche

From PHPUnit to Go: Data-Driven Unit Testing for Go Developers

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.

Warum der Datenanbieter-Ansatz?

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.

Wann wird die Verwendung von Datenanbietern dringend empfohlen?

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.

Schön, was genau ist ein Datenanbieter?

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.

Das Problem ohne Datenanbieter lösen?

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.

Lösung des Problems mit Datenanbieter!

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.

Bringen Sie Datenanbieter zum Mitnehmen

  • Go verfügt nicht über ein natives Datenanbietermodell wie PHPUnit, daher müssen wir einen anderen Ansatz verwenden. Es kann viele Implementierungen mit mehreren Komplexitätsebenen geben. Die folgende ist eine durchschnittliche Implementierung, die für die Simulation eines Datenanbieters im Go-Land in Frage kommen könnte
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])
        })

    }
}
  • Wir definieren grundsätzlich zwei Karten/Listen: eine für die Eingabedaten und die zweite für die erwarteten Daten. Wir stellen sicher, dass jedes Fallszenario auf beiden Seiten über denselben Kartenschlüssel auf beiden Seiten referenziert wird.
  • Das Ausführen der Tests ist dann eine Sache einer Schleife in einer einfachen Funktion, die die vorbereiteten Eingabe-/Erwartungslisten durchläuft.
  • Mit Ausnahme einiger einmaliger Standardtypen sollten Änderungen an Tests nur auf der Datenseite erfolgen. Meistens sollte keine Änderung die Logik der Funktion ändern, die Tests ausführt, und so die oben genannten Ziele erreichen: Reduzierung Testarbeiten bis hin zur Rohdatenaufbereitung.

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!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn