在這篇文章中,我們將探索如何將 PHP 單元測試思維,特別是 PHPUnit 框架的資料提供者方法引入 Go。如果您是經驗豐富的 PHP 開發人員,您可能熟悉資料提供者模型:在原始陣列中單獨收集測試資料並將這些資料輸入到測試函數中。這種方法使單元測試更乾淨、更易於維護,並遵守開放/封閉等原則。
為什麼採用資料提供者方法?
使用資料提供者方法在 Go 中建立單元測試具有多種優勢,包括:
增強的可讀性和可擴展性:測試變得可視化組織,頂部有清晰分隔的數組代表每個測試場景。每個數組的鍵描述場景,而其內容保存測試該場景的資料。這種結構使文件易於處理並且易於擴展。
關注點分離:資料提供者模型將資料和測試邏輯分開,從而產生一個輕量級、解耦的函數,隨著時間的推移,該函數可以基本保持不變。增加新場景只需要向提供者追加更多數據,保持測試功能對擴展開放,對修改關閉——開放/封閉原則在測試中的實際應用。
在某些專案中,我甚至看到了足夠密集的場景,足以保證使用單獨的 JSON 檔案作為資料來源,手動建立並提供給提供程序,而提供者又向測試函數提供資料。
什麼時候非常鼓勵使用數據提供者?
當您有大量具有不同資料的測試案例時,特別鼓勵使用資料提供者:每個測試案例在概念上相似,但僅在輸入和預期輸出方面有所不同。
在單一測試函數中混合資料和邏輯會降低開發人員體驗 (DX)。它通常會導致:
冗長過載:重複具有輕微資料變化的語句的冗餘程式碼,導致程式碼庫冗長而沒有額外的好處。
清晰度降低:當嘗試將實際測試資料與周圍程式碼隔離時,掃描測試函數變得很繁瑣,而資料提供者方法自然可以緩解這種情況。
很好,那麼數據提供者到底是什麼?
PHPUnit 中的 DataProvider 模式,基本上提供者函數為測試函數提供在隱式循環中使用的不同資料集。它確保了 DRY(不要重複自己)原則,並與開放/封閉原則保持一致,使得在不改變核心測試功能邏輯的情況下更容易添加或修改測試場景。
在沒有數據提供者的情況下解決問題?
為了說明冗長、程式碼重複和維護挑戰的缺點,以下是在沒有資料提供者幫助的情況下對冒泡排序函數進行單元測試的範例片段:
<?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)); } } </int></int>
使用數據提供者有什麼優勢嗎?哦,是的:
簡潔:所有測試資料都集中在一個方法中,無需針對每個場景使用多個函數。
增強可讀性:每個測試案例都組織良好,每個場景都有描述性的鍵。
開放/封閉原則:可以在不改變核心測試邏輯的情況下為資料提供者新增案例。
改進的 DX(開發人員體驗):測試結構乾淨、吸引人,甚至讓那些懶惰的開發人員也有動力去擴展、調試或更新它。
讓資料提供者走上正軌
- Go 沒有像 PHPUnit 這樣的原生資料提供者模型,因此我們需要使用不同的方法。可能有許多實現具有多個層級的複雜性,以下是一個平均實現,可能是模擬 Go land 中的資料提供者的候選者
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中文網其他相關文章!

Go語言的核心特性包括垃圾回收、靜態鏈接和並發支持。 1.Go語言的並發模型通過goroutine和channel實現高效並發編程。 2.接口和多態性通過實現接口方法,使得不同類型可以統一處理。 3.基本用法展示了函數定義和調用的高效性。 4.高級用法中,切片提供了動態調整大小的強大功能。 5.常見錯誤如競態條件可以通過gotest-race檢測並解決。 6.性能優化通過sync.Pool重用對象,減少垃圾回收壓力。

Go語言在構建高效且可擴展的系統中表現出色,其優勢包括:1.高性能:編譯成機器碼,運行速度快;2.並發編程:通過goroutines和channels簡化多任務處理;3.簡潔性:語法簡潔,降低學習和維護成本;4.跨平台:支持跨平台編譯,方便部署。

關於SQL查詢結果排序的疑惑學習SQL的過程中,常常會遇到一些令人困惑的問題。最近,筆者在閱讀《MICK-SQL基礎�...

golang ...

Go語言中如何對比並處理三個結構體在Go語言編程中,有時需要對比兩個結構體的差異,並將這些差異應用到第�...

GoLand中自定義結構體標籤不顯示怎麼辦?在使用GoLand進行Go語言開發時,很多開發者會遇到自定義結構體標籤在�...


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

SublimeText3 Linux新版
SublimeText3 Linux最新版

WebStorm Mac版
好用的JavaScript開發工具

禪工作室 13.0.1
強大的PHP整合開發環境

Atom編輯器mac版下載
最受歡迎的的開源編輯器