這個範例讓我們來看看怎麼用 TDD 來寫物件,不過我寫完程式之後才發現都沒看到什麼需要Refactor的地方,有點可惜沒有演示到這部分。
複習 TDD 步驟:
來寫一個可以計算多人薪水總和的程式。
思考之後預計會有一個Salary
類別,代表一個人的名字與薪水,以及PaymentList
類別,可以加入Salary
物件之後計算總和。
完整程式碼可以在Github取得,用$ git checkout
切換查看。
第一個要寫的是Salary
類別的 set 函式,類似範例一的呼叫函式,從最初的呼叫類別產生物件開始。
<?php
// Salary.php
// ---tests---
run_tests();
function run_tests(){
test_set_salary();
}
function test_set_salary(){
$tmp_salary = new Salary();
}
這次預期會有好幾個測試,因此會將每一段獨立的測試用一個 function 包起來,所有要跑的測試都放在run_tests()
中執行。
在 Terminal 執行我們的新測試,理所當然地印出了錯誤。
$ php Salary.php
PHP Fatal error: Uncaught Error: Class 'Salary' not found in Salary.php:13
定義Salary
類別。
<?php
// Salary.php
class Salary{
}
( 若有下載範例程式碼,用$ git checkout 2a
查看 )
test_set_salary()
要測試利用set_data()
是否的確有設定了Salary
物件的名字與薪水數值。
這個測試對於目前的物件來說其實意義不大,只要對語法有基本瞭解,大概就會知道這樣的設定當然沒問題。
但是當物件的設定函式越來越龐大時,或是之後對資料庫的存取,利用自動化測試確保函式的確有更新資料,是相當重要的。
<?php
// Salary.php
class Salary{
}
// ---tests---
run_tests();
function run_tests(){
test_set_salary();
}
function test_set_salary(){
$tmp_salary = new Salary();
$tmp_salary->set_data("Louis", 100);
assert($tmp_salary->name == "Louis");
assert($tmp_salary->value == 100);
}
PHP Fatal error: Uncaught Error: Call to undefined method Salary::set_data() in Salary.php:17
set_data()
將參數的名字跟數值設定到Salary
的變數中 [1]。
<?php
// Salary.php
class Salary{
public $name;
public $value;
public function set_data($_name, $_value){
$this->name = $_name;
$this->value = $_value;
}
}
( $ git checkout 2b
)
寫完了Salary
類別,開始進到PaymentList
類別,在函示中產生 instance。
<?php
// PaymentList.php
require 'Salary.php';
...
// ---tests---
run_payment_list_tests();
function run_payment_list_tests(){
test_insert_a_salary();
}
function test_insert_a_salary(){
$tmp_salary = new Salary();
$tmp_salary->set_data("Louis", 100);
$tmp_payments = new PaymentList();
}
PHP Fatal error: Uncaught Error: Class 'PaymentList' not found in PaymentList.php:18
<?php
// PaymentList.php
require 'Salary.php';
class PaymentList{
}
熟練之後,對於語法夠熟悉、覺得這樣的循環太繁瑣的話,這樣只有函式、物件名稱的循環可以省略掉。
test_insert_a_salary()
測試Salary
物件是否的確有被傳入PaymentList
物件的$list
變數中。
<?php
// PaymentList.php
...
// ---tests---
run_payment_list_tests();
function run_payment_list_tests(){
test_insert_a_salary();
}
function test_insert_a_salary(){
$tmp_salary = new Salary();
$tmp_salary->set_data("Louis", 100);
$tmp_payments = new PaymentList();
assert(count($tmp_payments->list) == 0);
$tmp_payments->insert_salary($tmp_salary);
assert(count($tmp_payments->list) == 1);
assert($tmp_payments->list[0]->name == "Louis");
}
PHP Notice: Undefined property: PaymentList::$list in PaymentList.php on line 22
PHP Warning: count(): Parameter must be an array or an object that implements Countable in PaymentList.php on line 22
Warning: count(): Parameter must be an array or an object that implements Countable in PaymentList.php on line 22
PHP Fatal error: Uncaught Error: Call to undefined method PaymentList::insert_salary() in PaymentList.php:24
可以發現這邊印出的錯誤訊息竟然不止一個,不過不用擔心。
insert_salary()
在$list
新增一個 element。
<?php
// PaymentList.php
require 'Salary.php';
class PaymentList{
public $list = array();
public function insert_salary($_salary){
$this->list[] = $_salary;
}
}
( $ git checkout 2c
)
最後的test_calculate_sum()
測試加總的結果是否相符。
<?php
// PaymentList.php
...
// ---tests---
run_payment_list_tests();
function run_payment_list_tests(){
test_calculate_sum();
test_insert_a_salary();
}
function test_calculate_sum(){
$tmp_salary_L = new Salary();
$tmp_salary_L->set_data("Louis", 100);
$tmp_salary_B = new Salary();
$tmp_salary_B->set_data("Bear", 150);
$tmp_payments = new PaymentList();
$tmp_payments->insert_salary($tmp_salary_L);
$tmp_payments->insert_salary($tmp_salary_B);
assert($tmp_payments->calculate_sum() == 250);
}
...
PHP Fatal error: Uncaught Error: Call to undefined method PaymentList::calculate_sum() in PaymentList.php:33
增加calculate_sum()
函式。
<?php
// PaymentList.php
require 'Salary.php';
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 2d
)
最初開始使用 TDD 時,同樣的程式一定會寫比平常久,因為跟自己原本寫程式的習慣跟思考方式很不一樣,同時也許讀者還沒幫自己的程式寫過自動化測試,TDD 與自動化測試,都需要花時間去練習。
題外話,因為 TDD 重複進行著寫測試->不通過->寫程式->通過,也有人稱呼 TDD 這樣的步驟為「紅綠燈」。
讀者可能會觀察到,這次範例中的測試,其實有不少重複的部分,
因此我們可以透過 測試框架 來增進測試的能力、以及編寫的速度,下一篇就來介紹測試框架的使用吧。
public
並不是個好作法,只是為了方便演示。這些小標題看不是很懂?
步驟 1.
步驟 2.
循環 2 - 步驟 1.
2 - 步驟 2.
3 - 步驟 1.
3 - 步驟 2.
4 - 步驟 1.
4 - 步驟 2.
5 - 步驟 1.
5 - 步驟 2.
完成!
每次的 [步驟 1.] 是指寫測試,[步驟 2.],是寫程式,
[步驟 1.] 跟 [步驟 2.] 進行了一次 TDD,[循環 2 - 步驟 1.]開始第二次 TDD,[3-步驟 1.] 則是第三次,
可能省略了一些字,看起來語意不統一,之後的文章試著換個比較清楚的方式說明。