この投稿では、PHP 単体テストの考え方、特に PHPUnit フレームワークのデータ プロバイダー アプローチを Go に導入する方法を検討します。経験豊富な PHP 開発者であれば、生の配列でテスト データを個別に収集し、このデータをテスト関数に供給するデータ プロバイダー モデルに精通しているはずです。このアプローチにより、単体テストがよりクリーンで保守しやすくなり、オープン/クローズなどの原則に準拠します。
Go で単体テストを構造化するためにデータ プロバイダーのアプローチを使用すると、次のようないくつかの利点があります。
可読性と拡張性の強化: テストが視覚的に整理され、上部に各テスト シナリオを表す明確に分離された配列が表示されます。各配列のキーはシナリオを記述し、そのコンテンツにはそのシナリオをテストするためのデータが保持されます。この構造により、ファイルは快適に作業でき、拡張も簡単になります。
懸念事項の分離: データ プロバイダー モデルはデータとテスト ロジックを分離し、その結果、時間が経ってもほとんど変化しない、軽量で分離された関数が得られます。新しいシナリオを追加するには、プロバイダーにさらにデータを追加するだけで済みます。これにより、テスト関数は拡張に対してオープンのままにし、変更に対してはクローズされます。これは、テストにおけるオープン/クローズ原則の実際的な適用です。
一部のプロジェクトでは、別の JSON ファイルをデータ ソースとして使用し、手動で構築してプロバイダーに供給し、プロバイダーがテスト関数にデータを供給することを正当化するほど高密度のシナリオも見てきました。
さまざまなデータを含む多数のテスト ケースがある場合は、データ プロバイダーの使用が特に推奨されます。各テスト ケースは概念的には似ていますが、入力と予想される出力が異なるだけです。
単一のテスト関数内でデータとロジックが混在すると、開発者エクスペリエンス (DX) が低下する可能性があります。多くの場合、次のような結果になります。
冗長オーバーロード: わずかなデータ変化を伴うステートメントを繰り返す冗長コードにより、追加の利点のない冗長なコードベースが生成されます。
明瞭さの低下: 実際のテスト データを周囲のコードから分離しようとすると、テスト関数をスキャンするのが面倒になりますが、データ プロバイダーのアプローチにより自然に軽減されます。
PHPUnit の DataProvider パターン。基本的にプロバイダー関数は、暗黙的なループで消費されるさまざまなデータセットをテスト関数に提供します。これにより、コア テスト機能のロジックを変更することなく、テスト シナリオの追加または変更が容易になり、DRY (Don't Reply Yourself) 原則が保証され、オープン/クローズド原則にも準拠します。
冗長性、コードの重複、メンテナンスの課題の欠点を説明するために、データ プロバイダーの助けを借りないバブル ソート関数の単体テストの例のスニペットを次に示します。
<?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])); } }
上記のコードに問題はありますか?確かに:
冗長性: 各テスト ケースには個別のメソッドが必要であり、その結果、コードベースが大規模で反復的になります。
重複: テスト ロジックは各メソッドで繰り返され、入力と予想される出力によってのみ異なります。
オープン/クローズド違反: 新しいテスト ケースを追加するには、さらにメソッドを作成してテスト クラス構造を変更する必要があります。
これは、データプロバイダーを使用するためにリファクタリングされた同じテストスイートです
<?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)); } }
データプロバイダーを使用する利点はありますか?そうそう:
簡潔性: すべてのテスト データが 1 つのメソッドに集中され、シナリオごとに複数の関数を使用する必要がなくなります。
可読性の向上: 各テスト ケースはよく整理されており、シナリオごとに説明的なキーが付いています。
オープン/クローズド原則: コアのテスト ロジックを変更せずに、新しいケースをデータ プロバイダーに追加できます。
改善された DX (開発者エクスペリエンス): テスト構造はすっきりしていて、目に魅力的で、怠惰な開発者でも拡張、デバッグ、更新する意欲を高めます。
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]) }) } }
ボーナス: このブログ投稿で紹介されているロジックを実装する Github リポジトリは、https://github.com/MedUnes/dsa-go にあります。これまでのところ、これらのテストを実行し、超有名な緑色のバッジを表示する Github アクションも含まれています ;)
次の有益な投稿でお会いしましょう!
以上がPHPUnit から Go へ: Go 開発者のためのデータ駆動型単体テストの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。