來看看用測試框架 PHPUnit 改寫範例二,有什麼差別。
範例二的題目:一個可以計算多人薪水總和的程式。
有一個Salary
類別,代表一個人的名字與薪水,以及PaymentList
類別,可以加入Salary
物件之後計算總和。
範例程式碼可以在Github取得,clone 之後用$ git checkout
切換查看。
一樣從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 會自動抓取所有符合規則的函式並執行。
PHPUnit\Framework\TestCase
寫類別與原本 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
)。
<?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)
這邊來對testInsertSalary()
做一點修改,由於接下來寫calculate_sum()
時一樣需要有一個已加入Salary
的PaymentList
,我們試著減少一點重複的程式碼。
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 當然還有更多功能,之後將持續在範例中說明。