相依的測試

在傳統的單元測試觀念中,測試方法應該是要獨立的。但後來也因為軟體的複雜變化,我們不再強調單元測試的獨立性,而是有系統地依照一連串的動作來進行測試。

分離測試方法

我們不太可能在一個測試方法裡把所有想測試的東西測完,一般來說會針對被測試類別的每個方法各寫一個測試。所以接著將之前的程式碼重寫如下:

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

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

class CartTest extends PHPUnit_Framework_TestCase
{
    public function testUpdateQuantitiesAndGetTotal()
    {
        $cart = new Cart();

        // Test 1
        $quantities = [
            1, 0, 0, 0, 0, 0,
        ];
        $cart->updateQuantities($quantities);
        $this->assertEquals(199, $cart->getTotal());

        // Test 2
        $quantities = [
            1, 0, 0, 2, 0, 0,
        ];
        $cart->updateQuantities($quantities);
        $this->assertEquals(797, $cart->getTotal());
    }

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

主要做了以下變動:

  • updateQuantitiesgetTotal 方法放在一起測試。
  • getProducts 方法獨立測試。
  • 測試方法名稱改為跟被測試方法相關連,例如 testUpdateQuantitiesAndGetTotaltestGetTotal

接著重新測試執行測試:

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

.F

Time: 33 ms, Memory: 3.00Mb

There was 1 failure:

1) CartTest::testGetProducts
Failed asserting that 0 matches expected 2.

C:\project\tests\CartTest.php:32

FAILURES!
Tests: 2, Assertions: 4, Failures: 1.

這裡可以看到測試失敗了,主要是因為每個測試都是獨立的,而先前我們對第四個商品數量的斷言就錯了。

$this->assertEquals(2, $products[3]['quantity']); // 第四個商品數量應為 0

這時候我們需要讓測試方法相依於另一個測試方法。

定義測試相依性

當測試方法之間有相依順序時, PHPUnit 提供 @depends 這個註記來定義測試方法之間相依關係;而被相依的測試需要回傳值來提供給下一個測試,下一個測試就可以用參數來接收這個回傳值。

因此上述的程式可以改成:

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

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

class CartTest extends PHPUnit_Framework_TestCase
{
    public function testUpdateQuantitiesAndGetTotal()
    {
        $cart = new Cart();

        // ... 略 ...

        return $cart;
    }

    /**
     * @depends testUpdateQuantitiesAndGetTotal
     */
    public function testGetProducts($cart)
    {
        $products = $cart->getProducts();
        $this->assertEquals(6, count($products));
        $this->assertEquals(2, $products[3]['quantity']);
    }
}
  • 首先在 testUpdateQuantitiesAndGetTotal 方法中,把最後的 $cart 物件回傳。

  • 而在 testGetProducts 方法的 DocBlock 上定義:

    @depends testUpdateQuantitiesAndGetTotal
    

    testGetProducts 方法可以接收 testUpdateQuantitiesAndGetTotal 方法的回傳值。

  • 假設測試 testGetProducts 方法相依其他多個測試方法,那麼這些方法的回傳值就會變成 testGetProducts 的參數;而參數順序就是依照 DocBlock 中 @depends 定義的順序。

官方手冊參考

練習

  • 思考單元測試中,獨立測試是重要的?

  • 思考為什麼測試方法之間需要有相依性?