Dans cet article, nous explorerons comment intégrer l'état d'esprit des tests unitaires PHP, en particulier l'approche du fournisseur de données du framework PHPUnit, dans Go. Si vous êtes un développeur PHP expérimenté, vous connaissez probablement le modèle du fournisseur de données : collecter les données de test séparément dans des tableaux bruts et introduire ces données dans une fonction de test. Cette approche rend les tests unitaires plus propres, plus maintenables et adhère à des principes tels que Ouvert/Fermé.
L'utilisation d'une approche de fournisseur de données pour structurer les tests unitaires dans Go offre plusieurs avantages, notamment :
Lisibilité et extensibilité améliorées : les tests sont organisés visuellement, avec des tableaux clairement séparés en haut représentant chaque scénario de test. La clé de chaque tableau décrit le scénario, tandis que son contenu contient les données permettant de tester ce scénario. Cette structure rend le fichier agréable à travailler et facile à étendre.
Séparation des préoccupations : le modèle de fournisseur de données maintient les données et la logique de test séparées, ce qui donne lieu à une fonction légère et découplée qui peut rester largement inchangée au fil du temps. L'ajout d'un nouveau scénario nécessite uniquement d'ajouter plus de données au fournisseur, en gardant la fonction de test ouverte aux extensions mais fermée pour modification – une application pratique du principe ouvert/fermé dans les tests.
Dans certains projets, j'ai même vu des scénarios suffisamment denses pour justifier l'utilisation d'un fichier JSON distinct comme source de données, construit manuellement et transmis au fournisseur, qui à son tour fournit des données à la fonction de test.
L'utilisation de fournisseurs de données est particulièrement encouragée lorsque vous disposez d'un grand nombre de scénarios de test avec des données variables : chaque scénario de test est conceptuellement similaire mais ne diffère que par l'entrée et la sortie attendue.
Le mélange des données et de la logique dans une seule fonction de test peut réduire l'expérience du développeur (DX). Cela conduit souvent à :
Surcharge de verbosité : code redondant qui répète des instructions avec de légères variations de données, conduisant à une base de code verbeuse sans avantage supplémentaire.
Clarté réduite : l'analyse de la fonction de test devient une corvée lorsque l'on tente d'isoler les données de test réelles du code environnant, ce que l'approche du fournisseur de données atténue naturellement.
Le modèle DataProvider dans PHPUnit où, essentiellement, la fonction fournisseur fournit à la fonction de test différents ensembles de données qui sont consommés dans une boucle implicite. Il garantit le principe DRY (Don't Repeat Yourself) et s'aligne également sur le principe ouvert/fermé, en facilitant l'ajout ou la modification de scénarios de test sans altérer la logique de la fonction de test de base.
Pour illustrer les inconvénients de la verbosité, de la duplication de code et des problèmes de maintenance, voici un extrait d'un exemple de test unitaire pour la fonction de tri à bulles sans l'aide des fournisseurs de données :
<?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])); } }
Y a-t-il des problèmes avec le code ci-dessus ? bien sûr :
Verbosité : chaque cas de test nécessite une méthode distincte, ce qui entraîne une base de code volumineuse et répétitive.
Duplication : La logique de test est répétée dans chaque méthode, variant uniquement en fonction de l'entrée et de la sortie attendue.
Violation ouverte/fermée : l'ajout de nouveaux cas de test nécessite de modifier la structure de la classe de test en créant plus de méthodes.
Voici la même suite de tests refactorisée pour utiliser un fournisseur de données
<?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)); } }
Y a-t-il des avantages à utiliser le fournisseur de données ? oh ouais :
Concision : Toutes les données de test sont centralisées dans une seule méthode, éliminant ainsi le besoin de plusieurs fonctions pour chaque scénario.
Lisibilité améliorée : Chaque cas de test est bien organisé, avec des clés descriptives pour chaque scénario.
Principe ouvert/fermé : de nouveaux cas peuvent être ajoutés au fournisseur de données sans altérer la logique de test de base.
DX (expérience de développement) amélioré : la structure des tests est propre, attrayante pour les yeux, ce qui motive même les développeurs paresseux à l'étendre, à la déboguer ou à la mettre à jour.
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 : un référentiel Github implémentant la logique présentée dans cet article de blog peut être trouvé ici https://github.com/MedUnes/dsa-go. Jusqu'à présent, il contient des actions Github exécutant ces tests et affichant même ce super célèbre badge vert ;)
À bientôt dans le prochain article informatif [espérons-le] !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!