測試驅動開發

前言

測試驅動開發是這樣一件事

1. 在寫任何產品程式碼前,先寫測試程式
2. 由於還沒有寫任何程式,所以通不過測試      ===> 紅
3. 使用任何手段,寫下通過測試的程式碼        ===> 綠
4. 使用任何你所知道的軟體工程手法重構程式碼 ===> 重構

tdd

TDD第一定律

我們相信撰寫測試程式所花掉的時間,會在後面除錯階段賺回來

TDD第二定律

我們相信TDD可以使我們得到更好的程式架構

TDD成長階段

1. 先寫測試,再寫程式
   這種事完全是天方夜譚
   打死不信

2. 半信半疑,但開始嘗試在專案中使用

3. 更進一步了解TDD所帶來的好處
   為自己的思考充滿漏洞而感到不可思議

4. 沒TDD會死
   開始懷疑並攻擊所有沒使用TDD的人
   對沒使用TDD寫的程式碼完全沒信心

5. 行於所當行,止於所不得不止
   何時該TDD,何時該放手去做
   在專案品質,預算,時間達成完美平衡

探索未知事務

一種探索真相的方法

1. 假設某程式碼可通過一系列的測試集合 T = { test_1 , test_2, test_3 ... test_n }

2. 通過 t(n+1) 組測試的程式碼
   應該比
   通過 t(n)測試的程式碼更為可信

3. 同理可以通過 t(n) 組的程式碼比可以通過 t(n-1) 組的更為可信

4. 測試程式若經過精心設計
   那麼我們可以使用一組合理數目的測試程式來"定位"出正確的程式碼

5. TDD是藉由寫測試程式來讓電腦告訴我們方向

一個簡單的範例

費伯納西數列


第一輪 (紅)

乖小孩要先寫測試程式

AssertTrue( f(1) == 1 );

還沒寫下任何程式碼,當然不會過關 ====> 紅


第二輪 (綠)

撰寫通過測試的程式碼,無論手段多卑鄙 (顯式實現法)

int f(int n)
{
    return 1;
}

卑鄙無恥,過關 ===> 綠


第三輪 (紅)

增加測試程式以拆穿和平的假象 (三角定位法)

AssertTrue( f(1)==1 );
AssertTrue( f(2)==1 );
AssertTrue( f(3)==2 );//測試不過 ===> 紅

正義終會戰勝邪惡


第四輪 (綠)

撰寫通過測試的程式碼,無論手段多卑鄙

int f(int n)
{
    if( n == 3 )
        return 2;
    else
        return 1;
}

卑鄙無恥,過關 ===> 綠


第五輪 (重構)

老師說魔術數字是不好的,所以我們重構程式碼

2 是什麼, 1 + 1 = 2
1 是什麼, f(1)=1 , f(2)=1

int f(int n)
{
    if( n == 3 )
        return f(1)+f(2);
    else
        return 1;
}

程式架構獲得改善


第六輪 (紅)

增加測試程式來伸張正義 (三角定位法)

AssertTrue( f(1)==1 );
AssertTrue( f(2)==1 );
AssertTrue( f(3)==2 );
AssertTrue( f(4)==3 );//測試不過 ===> 紅

第七輪 (綠)

某硬體工程師說寫程式就是if else

int f(int n)
{
    if( n == 4 )
        return 3;
    else if( n==3 )
        return f(1)+f(2);
    else
        return 1;
}

無能但可用 ===> 綠

第八輪 (重構)

前述程式碼有辱斯文,所以重構美化之

3 是什麼 2 + 1 =3
2 是什麼 f(3) = 2
1 是什麼 f(2) = 1

int f(int n)
{
    if( n == 4 )
        return f(2)+f(3);
    else if( n==3 )
        return f(1)+f(2);
    else
        return 1;
}

第九輪 (重構)

老師說重複的程式碼是不好的
所以我們合併條件式

int f(int n)
{
    if( n >= 3 )
        return f(n-2)+f(n-1);
    else
        return 1;
}

於是我們使用測試驅動開發得到費伯納西數列


TDD缺點

1.TDD Happy( 與之相對的有Pattern Happy )

誤以為TDD可以解決所有編程問題
除非恰巧你有無限時間和預算

2.架構複雜化

在TDD過程中會引入過多不必要中介層
俗話說殺雞用牛刀
違反TDD第一和第二定律

3.會失去整體視野

思維集中在 => 紅-綠-重購
只能取得局部戰勝利
但軟體是一個整體,取得所有局部戰勝利,不代表軟體可用

TDD的攻擊

1. 局部戰勝利不等總體戰

沒錯,但沒有局部勝利,就不會有總體勝利
在正正當當做生意的情況下
有好軟體的品質是成功事業的一環

當然確實有王牌業務,擅長把垃圾當黃金賣

2. 容易引入複雜架構而過度設計

人非聖賢,孰能無過
大家都是在失敗中學習成長
不使用TDD,就不會引入複雜設計嗎?

3. 寫測試很花時間

TDD是藉由寫測試程式讓電腦告訴我們錯了
錯誤越晚修正,修正成本越高

當然資深的架構師,由於在舊專案的失敗經驗
在新專案可以直接進入通靈狀態

結論

TDD是碼農邁向更好碼農的必修課程
斷不需因為它的缺點而停止學習和使用它
我們不能因為怕小孩會把玩具弄亂,而把玩具收起來

問: 碼農中的霸主是什麼?
答: 還是碼農

祝天下的碼農,能在工作中取得樂趣