第一次用 PHPUnit 寫測試就上手(下)

上篇文章:第一次用 PHPUnit 寫測試就上手(上)

3. Data Providers(資料提供者)

資料提供者,能提供多筆的測試資料給測試案例進行多次的測試。

使用資料提供者,能讓測試更簡潔,因為,可以將測試的 assertions 與測試資料分開寫。

* ● 測試 3 - 限制報名人數*

在一開始有提到,活動報名系統,會限制每個活動的報名人數。測試案例要測試多個不同報名人數的活動,如果報名成功,reserve() 會回傳 true,相反的報名失敗則回傳 false

src/PHPUnitEventDemo/Event.php


  這個 E-mail 地址已經被防止灌水惡意程式保護,您需要啟用 Java Script 才能觀看
  ';
        $user = new \PHPUnitEventDemo\User($userId, $userName, $userEmail);

        // 同一個使用者報名兩次
        $event->reserve($user);
        $event->reserve($user);
    }
}

EventTest 內增加一個 testDuplicatedReservationWithException() 測試案例,在註解內標註:

  1. @expectedException \PHPUnitEventDemo\EventException : 預期的異常類別。
  2. @expectedExceptionMessage Duplicated reservation : 預期的異常訊息。
  3. @expectedExceptionCode 1 : 預期的異常代碼。

也就是,預期在這個測試案例內會接收到 EventException 的異常類別、異常訊息為 Duplicated reservation,異常代碼為 1。

執行測試:

$ phpunit --bootstrap vendor/autoload.php tests/EventTest
PHPUnit 4.4.0 by Sebastian Bergmann.

.....

Time: 53 ms, Memory: 3.50Mb

OK (5 tests, 19 assertions)

5. Fixtures

Fixture 能協助測試時,需要用到的測試環境、物件的建立,在測試完後,把測試環境、物件拆解掉,還原到初始化前的狀態。

主要透過 setUp()tearDown() 分別來初始化測試與拆解還原到初始化前的狀態。

下面一樣利用 test/EventTest.php 來示範,先了解目前測試有哪些問題。

tests/EventTest.php


  這個 E-mail 地址已經被防止灌水惡意程式保護,您需要啟用 Java Script 才能觀看
  ';
        $user = new \PHPUnitEventDemo\User($userId, $userName, $userEmail);

        // ignore ...
    }

    // ignore ... 

    /**
     * @expectedException \PHPUnitEventDemo\EventException
     * @expectedExceptionMessage Duplicated reservation
     * @expectedExceptionCode 1
     */
    public function testDuplicatedReservationWithException()
    {
        // 測試重複報名,預期丟出異常

        $eventId = 1;
        $eventName = '活動1';
        $eventStartDate = '2014-12-24 12:00:00';
        $eventEndDate = '2014-12-24 13:30:00';
        $eventDeadline = '2014-12-23 23:59:59';
        $eventAttendeeLimit = 10;
        $event = new \PHPUnitEventDemo\Event($eventId, $eventName, $eventStartDate, $eventEndDate, $eventDeadline, $eventAttendeeLimit);

        $userId = 1;
        $userName = 'User1';
        $userEmail = '
 
  這個 E-mail 地址已經被防止灌水惡意程式保護,您需要啟用 Java Script 才能觀看
  ';
        $user = new \PHPUnitEventDemo\User($userId, $userName, $userEmail);

        // ignore ...
    }
}

注意 testReserve()testDuplicatedReservationWithException() 兩個測試案例,都需要在測試前建立 EventUser 物件,使用 setUp() 在測試前,建立兩個物件,測試完後,tearDown() 再把不需要的物件清空。

加入 fixtures 後

tests/PHPUnitEventDemo.php


  這個 E-mail 地址已經被防止灌水惡意程式保護,您需要啟用 Java Script 才能觀看
  ';
        $this->user = new \PHPUnitEventDemo\User($userId, $userName, $userEmail);
    }
    public function tearDown()
    {
        $this->event = null;
        $this->user = null;
    }
    public function testReserve()
    {
        // 測試報名

        // 使用者報名活動
        $this->event->reserve($this->user);

        $expectedNumber = 1;

        // 預期報名人數
        $this->assertEquals($expectedNumber, $this->event->getAttendeeNumber());

        // 報名清單中有已經報名的人
        $this->assertContains($this->user, $this->event->attendees);

        return $this->event;
    }

    // ignore ...

    /**
     * @expectedException \PHPUnitEventDemo\EventException
     * @expectedExceptionMessage Duplicated reservation
     * @expectedExceptionCode 1
     */
    public function testDuplicatedReservationWithException()
    {
        // 測試重複報名,預期丟出異常

        // 同一個使用者報名兩次
        $this->event->reserve($this->user);
        $this->event->reserve($this->user);
    }
}

$event$user 物件修改成全域變數,接著把建立物件寫在 setUp() 中,清空物件寫在 tearDown(),再將 原本 testReserve()testDuplicatedReservationWithException() 中的 建立 $event$user 物件程式移掉,且使用到這兩個變數改成使用全域變數,也就是 $this->event$this->user

所以在執行測試的時候,運作順序會是:
setUp()testReserve()tearDown() → … → setUp()testDuplicatedReservationWithException()tearDown()

五、設定 PHPUnit

在前面此用 PHPUnit 工具來執行測試時,有用到 –bootstrap,在執行測試前先執行 vendor/autoload.php 程式來註冊 autoloading 的 function。可是每次執行測試,都要加上參數有點麻煩,所以,PHPUnit 可以利用 XML 設定檔來設定。

將 phpunit.xml 設定檔放在專案目錄下,與 src、tests 同一層。

phpunit.xml


    bootstrap="./vendor/autoload.php">
    
         name="MyEventTests">
            ./tests/EventTest.php
        
     

  • : 加入 bootstrap 屬性,對應到的值就是要執行的程式檔案。
  • : 在專案底下,能採用不同的測試組合。由一至多個的 。 組成。
  • : name 屬性,設定測試組合的名稱。測試組合內會包括許多測試程式檔案。

執行測試,如果 XML 設定檔檔名不是 phpunit.xml 的話,可以利用 --configuraton 來指定 XML 設定檔的路徑,如果檔名是 phpunit.xml ,就能省略不指定。

$ phpunit --configuration phpunit.xml tests/EventTest

也可以執行不同的測試組合。

$ phpunit MyEventTests

還有更多 XML 設定檔可以使用,參考:https://phpunit.de/manual/current/en/appendixes.configuration.html

六、Code Coverage 分析

撰寫好單元測試之後,該如何了解到哪些目標程式還沒有經過測試?目標程式被測試百分比有多少?

PHPUnit 是利用 PHP_CodeCoverage 來計算程式碼覆蓋率 (Code coverage),需要安裝 Xdebug。

該如何產生 Code coverage 呢?
先在專案底下建立一個 reports/ 目錄,存放 Code coverage 分析的結果。

$ phpunit --bootstrap vendor/autoload.php phpunit.xml --coverage-html reports/ tests/

當然,也可以使用 XML 設定檔來設定。

phpunit.xml


    bootstrap="./vendor/autoload.php">
    
         name="MyEventTests">
            ./tests/EventTest.php
        
    
    
         type="coverage-html" target="reports/" charset="UTF-8"/>
    

接著執行測試:

$ phpunit tests/

就可以在 reports/ 下打開 index.html 或其他 HTML 檔案,瀏覽 Code coverage 分析的結果。

PHP-3 resized

更多資料



您也許有興趣閱讀以下文章:




自由軟體鑄造場電子報 : 第 257 期 第一次用 PHPUnit 寫測試就上手(下)

分類: 技術專欄