iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 6
0

來看看用測試框架 PHPUnit 改寫範例二,有什麼差別。

範例二的題目:一個可以計算多人薪水總和的程式。
有一個Salary類別,代表一個人的名字與薪水,以及PaymentList類別,可以加入Salary物件之後計算總和。

範例程式碼可以在Github取得,clone 之後用$ git checkout切換查看。


1. Salary::set_data()

一樣從Salary類別的set_data設定函式開始,之後我們就不加入呼叫空函式的過程了。

紅燈 (寫測試)

<?php
// tests/SalaryTest.php

use PHPUnit\Framework\TestCase;
use Src\Salary;

class SalaryTest extends TestCase
{
    public function testSetData()
    {
        $s = new Salary();
        $s->set_data("Louis", 100);
        $this->assertSame("Louis", $s->name);
        $this->assertSame(100, $s->value);
    }
}

複習一下,PHPUnit 的測試命名規則,PHPUnit 會自動抓取所有符合規則的函式並執行。

  1. 檔案需要以 Test.php 結尾
  2. 要使用 PHPUnit 的測試要繼承PHPUnit\Framework\TestCase寫類別
  3. 以及以 test 開頭的成員函式(須為 public )

與原本 PHP 原生的assert()語法不同,用$this->assertXXXX()的方式,assertSame()代表兩個傳入的參數要相等,其他還有很多種方便的 assert 函式,之後還會陸續看到。

範例程式碼包含了use一個namespace的語法,意思是使用這個命名空間中的這個類別(檔案),與 include 是有點相似的概念,但這是不同的機制。依照PSR-4的規則,就不在文章中多佔篇幅了 (雖然花了我不少時間搞懂),可以參考這篇PHP PSR-4 Autoloader 機制

使用以下指令來執行測試。

$ ./vendor/bin/phpunit tests/

得到了不同於以往簡陋的錯誤訊息,多了執行測試的整理。

E                1 / 1 (100%)

Time: 87 ms, Memory: 4.00 MB

There was 1 error:

1) SalaryTest::testSetData
Error: Call to undefined method Src\Salary::set_data()

tests/SalaryTest.php:12

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

綠燈 (寫程式)

<?php
namespace Src;

class Salary{
    public $name;
    public $value;
    public function set_data($_name, $_value)
    {
        $this->name = $_name;
        $this->value = $_value;
    }
}

( 若有下載範例程式碼,用$ git checkout 3a查看 )

主程式當然沒有什麼變化,畢竟上一個範例就有了,除了 namespace 的使用以外。

而 PHPUnit 執行結果則有些變化。

.                1 / 1 (100%)

Time: 98 ms, Memory: 4.00 MB

OK (1 test, 2 assertions)

從原本的 E (Error) 變成了 . (Pass),Error 是因為無法執行測試(無法呼叫函式),如果是 assert 沒通過 (e.g. assertSame(1, 0)),則會是 F (Fail)的字樣。

也可以加入--testdox來顯示執行的名稱,

$ ./vendor/bin/phpunit tests/ --testdox
Salary
 ✔ Set data

Time: 37 ms, Memory: 4.00 MB

OK (1 test, 2 assertions)

會顯示執行了總共哪些測試,顯示的文字是根據測試名稱而來 ( testSetData)。

2. PaymentList::insert_salary()

紅燈

<?php

use PHPUnit\Framework\TestCase;
use Src\PaymentList;
use Src\Salary;

class PaymentListTest extends TestCase
{
    public function testInsertSalary()
    {
        $s = new Salary();
        $s->set_data("Louis", 100);

        $pl = new PaymentList();
        $this->assertSame(0, count($pl->list));

        $pl->insert_salary($s);
        $this->assertSame(1, count($pl->list));
        
        $this->assertSame("Louis", $pl->list[0]->name);        
    }
}

綠燈

<?php
namespace Src;

class PaymentList
{
    public $list = array();
    public function insert_salary($_salary)
    {
        $this->list[] = $_salary;
    }
}

( $ git checkout 3b )

..                2 / 2 (100%)

Time: 77 ms, Memory: 4.00 MB

OK (2 tests, 4 assertions)

3. PaymentList::calculate_sum()

紅燈

這邊來對testInsertSalary()做一點修改,由於接下來寫calculate_sum()時一樣需要有一個已加入SalaryPaymentList,我們試著減少一點重複的程式碼。

public function testInsertSalary()
{
    $s = new Salary();
    $s->set_data("Louis", 100);
    $s_2 = new Salary();
    $s_2->set_data("Bear", 150);  // 加入兩個 Salary物件

    $pl = new PaymentList();
    $this->assertSame(0, count($pl->list));

    $pl->insert_salary($s);
    $pl->insert_salary($s_2);
    $this->assertSame(2, count($pl->list));

    $this->assertSame("Louis", $pl->list[0]->name);
    return $pl;  // 回傳 PaymentList
}

接著加入testCalculateSum()

/**
 * @depends testInsertSalary
 */
public function testCalculateSum($pl)
{
    $this->assertSame(250, $pl->calculate_sum());
}

這是 PHPUnit 的 Annotations 語法,那段看起來像是註解的部分,實際上對測試有影響,定義了testCalculateSum()依賴於testInsertSalary(),因此可以拿到後者 return 的回傳值。

這麼一來,重複的程式碼減少了。不過這麼做也有風險,這兩個測試有相依性,會變得不容易修改。

綠燈

class PaymentList
{
    ...
    
    public function calculate_sum(){
        $result = 0;
        for ($i = 0; $i<count($this->list); $i++){
            $result += $this->list[$i]->value;
        }
        return $result;
    }
}

( $ git checkout 3c )

...                3 / 3 (100%)

Time: 95 ms, Memory: 4.00 MB

OK (3 tests, 6 assertions)

完成!

學習使用測試框架,能夠為編寫測試帶來一些幫助,PHPUnit 當然還有更多功能,之後將持續在範例中說明。


上一篇
測試框架 (PHPUnit)
下一篇
插入曲:關於 Interface 與 public / private
系列文
如何一步步實踐TDD (測試驅動開發)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言