PHPUnit 資料提供者

測試方法通常需要測試資料。要完全測試某些方法,你需要為每個可能的測試條件提供不同的資料集。當然,你可以使用迴圈手動執行此操作,如下所示:

...
public function testSomething()
{
    $data = [...];
    foreach($data as $dataSet) {
       $this->assertSomething($dataSet);
    }
}
... 

有人可以發現它很方便。但是這種方法存在一些缺點。首先,如果測試函式接受多個引數,則必須執行其他操作來提取資料。其次,在失敗時,如果沒有附加訊息和除錯,就很難區分出故障資料集。第三,PHPUnit 提供了使用資料提供程式處理測試資料集的自動方法。

資料提供程式是一個函式,應返回特定測試用例的資料。

資料提供程式方法必須是公共的,並返回一個陣列陣列或一個實現 Iterator 介面的物件,併為每個迭代步驟生成一個陣列。對於作為集合一部分的每個陣列,將呼叫測試方法,並將陣列的內容作為其引數。

要在測試中使用資料提供程式,請使用 @dataProvider annotation 以及指定的資料提供程式函式的名稱:

/**
* @dataProvider dataProviderForTest
*/
public function testEquals($a, $b)
{
    $this->assertEquals($a, $b);
}

public function dataProviderForTest()
{
    return [
        [1,1],
        [2,2],
        [3,2] //this will fail
    ];
}

陣列陣列

請注意,dataProviderForTest() 返回陣列陣列。每個巢狀陣列都有兩個元素,它們將逐一填充 testEquals() 的必要引數。如果沒有足夠的元素,這樣的錯誤將被丟擲 Missing argument 2 for Test::testEquals()。PHPUnit 將自動迴圈資料並執行測試:

public function dataProviderForTest()
{
    return [
        [1,1], // [0] testEquals($a = 1, $b = 1)
        [2,2], // [1] testEquals($a = 2, $b = 2)
        [3,2]  // [2] There was 1 failure: 1) Test::testEquals with data set #2 (3, 4)
    ];
}

為方便起見,可以命名每個資料集。檢測失敗的資料會更容易:

public function dataProviderForTest()
{
    return [
        'Test 1' => [1,1], // [0] testEquals($a = 1, $b = 1)
        'Test 2' => [2,2], // [1] testEquals($a = 2, $b = 2)
        'Test 3' => [3,2]  // [2] There was 1 failure: 
                           //     1) Test::testEquals with data set "Test 3" (3, 4)
    ];
}

迭代器

class MyIterator implements Iterator {
    protected $array = [];

    public function __construct($array) {
        $this->array = $array;
    }

    function rewind() {
        return reset($this->array);
    }

    function current() {
        return current($this->array);
    }

    function key() {
        return key($this->array);
    }

    function next() {
        return next($this->array);
    }

    function valid() {
        return key($this->array) !== null;
    }
}
...

class Test extends TestCase
{
    /**
     * @dataProvider dataProviderForTest
     */
    public function testEquals($a)
    {
        $toCompare = 0;

        $this->assertEquals($a, $toCompare);
    }

    public function dataProviderForTest()
    {
        return new MyIterator([
            'Test 1' => [0],
            'Test 2' => [false],
            'Test 3' => [null]
        ]);
    }
}

如你所見,簡單的迭代器也可以工作。

請注意,即使對於單個引數,資料提供者也必須返回陣列 [$parameter]

因為如果我們將 current() 方法(實際上每次迭代返回資料)更改為:

function current() {
    return current($this->array)[0];
}

或者更改實際資料:

return new MyIterator([
            'Test 1' => 0,
            'Test 2' => false,
            'Test 3' => null
        ]);

我們會收到一個錯誤:

There was 1 warning:

1) Warning
The data provider specified for Test::testEquals is invalid.

當然,在簡單陣列上使用 Iterator 物件是沒有用的。它應該為你的案例實現一些特定的邏輯。

發電機

它沒有明確說明並在手冊中顯示,但你也可以使用生成器作為資料提供者。請注意,Generator 類實際上實現了 Iterator 介面。

這裡有一個使用 DirectoryIteratorgenerator 結合的例子:

/**
 * @param string $file
 *
 * @dataProvider fileDataProvider
 */
public function testSomethingWithFiles($fileName)
{
    //$fileName is available here
    
    //do test here
}

public function fileDataProvider()
{
    $directory = new DirectoryIterator('path-to-the-directory');

    foreach ($directory as $file) {
        if ($file->isFile() && $file->isReadable()) {
            yield [$file->getPathname()]; // invoke generator here.
        }
    }
}

注意提供者 yields 是一個陣列。你將收到無效的資料提供者警告。