瞄準、射擊(Aim, Fire)

原作者:Kent Beck

翻譯:Areca Chen

「如果你沒有一個可中斷程式的測試案例,決不要為功能寫一行程式碼」--Kent Beck

「先寫測試的程式設計(test-first coding)方式不是一種測試的技術」--Word Cunninghan

Craig Larman 談到有關佛教的一些境界--獨掌之聲(譯註1)及見山不是山 (譯註2)等等。現在我提供一個:先寫測試的程式設計方式不是測試(Test-first coding isn't testing)。OK, Ward Cunningham那是甚麼?不要介意那是甚麼--而是在我的程式碼中將要做些甚麼?

我們必須從如何讓它可以運作開始。讓我們說我們要寫一個功能;這個功能要把兩個數加起來。我們不直接跳進這個功能,我們先問「我們如何測試?」你認為這個程式碼的片段怎麼樣:

assertEquals(4, sum); // 請參考 JUnit.org中有更多的說明

這是一個好的開始,但我們如何計算sum,下面這個如何:

int sum=? ...嗯.…

如果我們以Calculator類別中的一個靜態方法表達我們的計算會怎樣?

  int sum= Calculator.sum(2, 2);

  assertEquals(4, sum);

這個片段無法編譯?真可惜。現在我們繼續撰寫我們的Calculator類別。

還有一大串的測試案例同時需要通過?很好!做成一個列表同時我們將全部通過他們,一次一個。

歷史軌跡

先寫測試的程式設計方式不是新出現的作法。幾乎是在有程式設計開始就有的。當我還是小孩子的時候我就從一本教導程式設計的書中學到了。書中說道你的程式設計是輸入磁帶(你應該知道就像一條長長的帶狀的磁 盤)並且在輸出的磁帶列印你所期望的。然後你撰寫程式以便得到輸出的磁帶是你所期望的。

在我開始應用先寫測試再程式設計以前,我不知道有多久我是在不知道正確的答案是甚麼的情況下設計程式。很瘋狂,不是嗎?你不應該做這樣的事,但或許你有一個合作伙伴是這樣做的。

分析

先寫測試是一種分析的技術。我們先決定我們的程式要設計甚麼以及甚麼是我們不要設計的,同時決定甚麼是我們預期的答案。從範圍中挑出的及;更重要的;甚麼是超出範圍的是軟體發展的關鍵。 當你撰寫程式時虔誠的遵循先寫測試迫使你明確的指出你所考慮的情況。別期望任何情況是不受讓測試之一可以運轉所影響。

設計

等等,還有。先寫測試也是一種設計的技術。你還記得我們何時決定sum應該是Calculator類別的一個靜態方法嗎?那是邏輯 、或介面,設計--就像我的舊大學課本談到的。當然,這些課本從未解釋如何邏輯設計(logical design);他們只是使設計更清晰;而這個清晰度是設計應該做的,它必須在實體設計(physical design)及實作之前完成,而上天會幫助任何混合它們的人。

先寫測試是邏輯設計的一種技術。在你尚未有實作之前,因此你必須在實體設計展開以便工作。當你開始打入的是一個表達邏輯的外觀此時你即將要寫的是邏輯設計。

Robert Martin以病毒(譯註3)作為比喻。病毒有特殊的形狀是設計來附著於細胞膜表面的微小顆粒。其形狀是相對於細胞膜表面微小顆粒的形狀。 先寫測試的測試方式就類似如此。我們要寫的測試是相對程式碼形狀的程式碼。同時,當形狀正確的相配,我們所寫的程式碼就是我們所期待的。

不做測試

這樣的測試你的付出有多少?別回答這個問題。還有更多。我是比較杞人憂天。當我(未)讓我的測試空白(blankie),我永遠在煩惱我是否錯失了甚麼,我忘記了甚麼,我是否弄亂了甚麼?有了測試,太棒了,我馬上有了信心。

那是否只是一個錯誤。按下按鍵,執行測試,顯示出綠色的訊號,程式碼已清理乾淨。嗯,下一個測試。

所有教你測試的老師大概都是想磨利你的刀子。「這不是測試,測試是對等(equivalence)類別及邊界案例(edge cases)及統計學上的品質控制及...及....一大堆你不瞭解的其他材料。

請暫停一下--我從沒說先寫測試是一種測試技術。事實上,如果我的記憶沒錯,我過去明確的說明那不是。

不管如何,仔細考慮介於撰寫程式前先寫測試及實際上在撰寫程式之後立即自動測試的差異,我想我有一個重要性的順序(order-of-magnitude)有些許背離撰寫程式前 先寫測試。我是否有硬性的數字(hard numbers)?沒有,我沒有。那是博士的工作,而我也沒有這些博士學位的其中之一。(那將是一個大論文,Laurie Williams在雙人組程式設計有這方面的研究。)

我不建議這是每一個人應該始終在做的事情。例如;我曾嘗試撰寫GUI的先寫測試(另一個論文,一個熱心的學生所寫)。既使如此那也是值得嘗試。

更好的設計

我過去看到先寫測試如何驅動設計決定。我感到很訝異!不止決定在不同時間形成,決定的本身也不同。先寫測試的程式碼比那些測試未與撰寫程式循環緊密結合的程式碼偏向於更有內聚力及低耦合。

有些人擔心他們甚至不能完成程式碼,而他們現在還必須要測試;且我們還要求他們做更好的設計。那是不可能完成的。

放心。你不需要是一個大設計師--你只需要是建設性的偷懶(creatively lazy)。這裡有一個範例。

假設我們要寫一個函數,給一個人有確定的年紀,傳回這個人的死亡率表(mortality table)(蠻陰森的,我知道,但這是你的保險)。我們一開始可能這麼寫:

assertEquals(expectedTable, actualTable);

我們如何計算ActualTable?詢問一個MortalityTable類別

MortalityTable actualTable= new MortalityTable(bob);

assertEquals(expectedTable, actualTable);

我們如何構建這Person物件?這裡有一個建構式含有11個參數(或11Set的方法,所有的Set的方法都需被呼叫,他們產生的結果都是相同的事情。)

Person bob= new Person(“Bob”, “Newhart”, 48, Person.NON_SMOKER,

以這種方式逐一打入參數實在很辛苦,我們不再喜歡這種為客戶先打入參數的方式。有沒有其他解決方式?

這裡可能有一種較簡單的方式建構Person物件,但沒有更簡單的解決方式:改變MortalityTable的介面以獲得年紀(架設那是唯一有關的參數,記住,這是個範例),而不是Person的所有參數。現在我無須建構Person的物件:

MortalityTable actualTable= new MortalityTable(48);

assertEquals(expectedTable, actualTable);

再忍耐一下

這會怎麼樣?我們做了一個很簡單的設計決定;這個決定在建構一個MortalityTable時比我們所需要建構傳遞整個Person有更多的彈性。沒有立即的回饋,我們可能幾年或幾十年都不會質疑這個問題而下這個決定。撰寫程式可以每秒鐘給我們回饋以便促使我們質疑我們的假設。

這是它美妙的地方--我無須是出色的有先見之明的設計師才能找到低耦合的設計。我們只需要單純的(intolerant of pointless effort)撰寫測試。我們還需要實證的(demonstrate)技巧以找尋及執行我們各式不同的設計。因此那不是設計技巧的終 點--回饋的強度取代正確猜測的能力。我們無須猜測(呃,不是長期的)有關正確的設計。我們設計一些簡單而我們認為可以達成目的東西,幾秒鐘之後我們得到確認。如果回饋顯示我們太過簡單,我們讓設計複雜一點。如果回饋顯示我們設計過渡(overdesigned),我們簡化它。

因此,先寫測試

就這些嗎?我留給你兩個更深入的觀點。

首先,先寫測試幫助你與你成對的工作伙伴清楚的溝通。Martin與我有一個極好的例子;有一次當我們在Munich設計程式。他要的期望值是0.1而我要的是10。你可以點出差異嗎 ?(這花費我們好一段時間瞭解我們只是使用不同的百分率表達方式,但這些程式對你們是公開的啊。)

其次,先寫測試為未來留下羅塞達石(Rosetta stone)(譯註4),回答所有重大的問題「當時我怎麼會這樣想?」

甚麼時候我們不用先寫測試?撰寫使用者介面時先寫測試是困難的。我曾想要找尋一個UI測試的策略;其中先寫測試實質上對我們設計更好的介面有所幫助。

給它一個空間,看看他如何走。

Kent Bect是Three Rivers Institute的經理,該機構研究技術、商業及人的互動。他是倡導XP、CRC卡、時間旅行樣式(TimeTravel patterns)、各種測試框架(xUnit家族)包括獲獎的Junit,及Hillside Group。他曾撰寫「Planning Extreme Programming, Extreme Programming Explained: Embrace Change」、「Kent Beck's Guide to Better Smalltalk: A Sorted Collection」及「Smalltalk Best Practice Patterns」等書籍。他的聯繫地址及電子信箱:PO Box 128, Merlin, OR 97532; kent@threeriversinstitute.org

Send general comments and any questions about the IEEE Computer Society's Web site to help@computer.org.
Read our
Privacy and Security guidelines.

This site and all contents (unless otherwise noted) are Copyright ©2001, IEEE, Inc. All rights reserved.

 


譯註1:佛教禪宗的一個公案,要參公案的人思考甚麼是一隻手掌拍出的聲音。

譯註2:此句說明人對事物的瞭解可分成三種境界:「見山是山;見水是水。」「見山不是山;見水不是水。」「見山是山;見水是水。」此處用以比喻Kent對於 先寫測試有了進一步的認知。

譯註3:這裡的病毒非電腦病毒,指的是生物的病毒。

譯註4:提供解釋古埃及象形文字的可靠線索的石頭。

本文獲授權同意翻譯函:

 

I would be honored if you would spend the time to translate my poor papers into Chinese and put them on the net.
 
Kent

 

Dear Kent,
I've read the articles "Test Infected:  Programmers Love Writing Tests" and " Aim, Fire"on the web site and like those very much.
It will be great if I could translate those into traditional Chinese and publish on our own site http://www.dotspace.idv.tw) to help those who want to explore XP more. I will, of course, declare the source and "hyperlink" those to your original documents. I'll be very grateful if you could give your authorization for me to do this.
Our web site contents about software engineering topics like XP, UML, Rup, Design Patterns, Framework, those are all in traditional Chinese.
        Regards,
Areca Chen
2002/2/8