對異常進行測試

對於一些我們能預見的異常行為 (Exception) ,程式都應該要處理;那麼如何確保我們的程式有抓到這些異常呢?測試就可以協助我們做到這件事。

建立異常處理程式

我們的範例中有個問題,那就是沒有檢查數量小於 0 的部份。因此我們要針對這部份加強它。

首先是修改 Cart.php ,加入數量的判斷:

<?php

class Cart
{

    // ...

    public function updateQuantities($quantities)
    {
        foreach ($quantities as $key => $qty) {
            if (!is_numeric($qty) || (int) $qty < 0) {
                throw new CartException("數量不正確,請輸入 0 或 0 以上的整數", 1);
            }

            // ...
        }

        // ...
    }

    // ...
}

class CartException extends Exception
{
}

然後在 index.php 中捕捉它:

<?php
// ...
if ('POST' === $_SERVER['REQUEST_METHOD']) {

    try {
        $cart->updateQuantities($_POST['quantity']);
        $_SESSION['cart'] = serialize($cart);
    } catch (CartException $e) {
        header('Content-Type: text/plain; charset="utf-8"');
        echo $e->getMessage();
        exit;
    }

    header('Location: /');
    exit;
}
//...

使用瀏覽器測試,在任一數量輸入框輸入負值後,就會得到錯誤訊息。

但怎麼在測試中測得異常狀況呢?

測試異常

在 PHPUnit 中,可以透過定義 @expectedException 註記來告知這個測試會產生預期的異常。因此,在測試裡我們加入了一個 testUpdateQuantitiesWithException 方法。

<?php

class CartTest extends PHPUnit_Framework_TestCase
{

    // ...

    /**
     * @expectedException CartException
     */
    public function testUpdateQuantitiesWithException()
    {
        $cart = new Cart();
        $quantities = [ -1, 0, 0, 0, 0, 0 ];
        $cart->updateQuantities($quantities); // 預期會產生一個 Exception
    }

    // ...

}
  • @expectedException 後面接一個 Exception 類別名稱,也可以是萬用 Exception 類別。

  • 如同 catch 敘述,不同的 Exception 類別都需要一個測試方法。

  • 如果標註了 @expectedException ,但卻沒有發生異常,那麼就是程式邏輯有 bug 。

  • @expectedExceptionMessage@expectedExceptionCode 則分別代表預期的異常訊息與預期的異常代號。

另外也可以在測試中以 $this->setExpectedException('ExceptionClassName'); 來設定程式預期會丟出的異常,例如:

<?php

class CartTest extends PHPUnit_Framework_TestCase
{

    // ...

    public function testUpdateQuantitiesWithException()
    {
        $this->setExpectedException('CartException');
        $cart = new Cart();
        $quantities = [ -1, 0, 0, 0, 0, 0 ];
        $cart->updateQuantities($quantities); // 預期會產生一個 Exception
    }

    // ...

}

官方手冊參考

練習

  • 思考哪些狀況可稱為異常?

  • 思考看看 PHPUnit 不在測試裡使用 try ... catch 來捕捉異常的理由。