Data Providers (數據提供者)

類似的斷言太多寫起來感覺很麻煩,可以用數據提供者 (Data Providers) 來提供測試數據。

改用數據提供者

我們可以在測試方法的 DocBlock 上使用 @dataProvider 註記來告訴測試方法從何處取得測試數據,然後再一一代入測試方法的參數中。原來的程式修改如下:

<?php
/* tests/CartTest.php */

require __DIR__ . '/../Cart.php';

class CartTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider provider
     */
    public function testUpdateQuantitiesAndGetTotal($quantities, $expected)
    {
        $cart = new Cart();

        $cart->updateQuantities($quantities);
        $this->assertEquals($expected, $cart->getTotal());

        return $cart;
    }

    public function provider()
    {
        return [
            [ [ 1, 0, 0, 0, 0, 0 ], 199 ],
            [ [ 1, 0, 0, 2, 0, 0 ], 797 ],
        ];
    }

    /**
     * @depends testUpdateQuantitiesAndGetTotal
     */
    public function testGetProducts($cart)
    {
        $products = $cart->getProducts();
        $this->assertEquals(6, count($products));
        $this->assertEquals(2, $products[3]['quantity']);
    }
}

主要修改的部份有:

  1. 加入 provider 方法,它將提供一個包含多組測試數據的數據集。每一組數據即表示要傳到 testUpdateQuantitiesAndGetTotal 方法的參數。

  2. testUpdateQuantitiesAndGetTotal 方法的 DocBlock 加入 @dataProvider provider 來取得測試數據。

執行結果如下:

C:\project> phpunit tests/CartTest
PHPUnit 4.2.5 by Sebastian Bergmann.

..PHP Fatal error:  Call to a member function getProducts() on a non-object in C:\project\tests\CartTest.php on line 34

結果發生錯誤,因為使用了 Data Provider 的測試,它的輸出將無法注入到其他相依於它的測試。

我們暫時把相依性拿掉,並修改斷言的預期結果:

<?php
/* tests/CartTest.php */

require __DIR__ . '/../Cart.php';

class CartTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider provider
     */
    public function testUpdateQuantitiesAndGetTotal($quantities, $expected)
    {
        $cart = new Cart();
        $cart->updateQuantities($quantities);
        $this->assertEquals($expected, $cart->getTotal());
    }

    public function provider()
    {
        return [
            [ [ 1, 0, 0, 0, 0, 0 ], 199 ],
            [ [ 1, 0, 0, 2, 0, 0 ], 797 ],
        ];
    }

    public function testGetProducts()
    {
        $cart = new Cart();
        $products = $cart->getProducts();
        $this->assertEquals(6, count($products));
        $this->assertEquals(0, $products[3]['quantity']);
    }
}

再次執行測試就成功了:

C:\project> phpunit tests/CartTest
PHPUnit 4.2.5 by Sebastian Bergmann.

...

Time: 25 ms, Memory: 3.00Mb

OK (3 tests, 4 assertions)

使用數據提供者的注意事項

  • Data Providers 的參數將優先於來自所依賴的測試的參數,而非 DocBlock 上定義的順序。

  • 來自於所依賴的測試的參數對於每個數據集都是一樣的。

  • 當一個測試依賴於另外一個使用了 Data Provider 的測試時,僅當被依賴的測試至少能在一組數據上成功時,依賴於它的測試才會運行。

官方手冊參考

練習

  • 試著在 provider 方法中加入一些數據組,然後測試看看是否符合預期。

  • 在官方手冊的 Data Provider 一節提到了使用 PHP Iterator 來做為數據提供者,試著實作看看。