首頁  >  文章  >  後端開發  >  從 PHPUnit 到 Go:Go 開發人員的資料驅動單元測試

從 PHPUnit 到 Go:Go 開發人員的資料驅動單元測試

Patricia Arquette
Patricia Arquette原創
2024-11-12 19:21:02197瀏覽

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

在這篇文章中,我們將探索如何將 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));
    }
}

使用數據提供者有什麼優勢嗎?哦,是的:

簡潔:所有測試資料都集中在一個方法中,無需針對每個場景使用多個函數。

增強可讀性:每個測試案例都組織良好,每個場景都有描述性的鍵。

開放/封閉原則:可以在不改變核心測試邏輯的情況下為資料提供者新增案例。

改進的 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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn