軟件開發方法學的泰斗Kent Beck先生最爲推崇"模式、極限編程和測試驅動開發"。在他所創造的極限編程(XP)方法論中,就向你們推薦"測試先行"這一最佳實踐,而且還專門撰寫了《測試驅動開發》一書,詳細說明如何實現。測試驅動開發是極限編程的重要特色,它以不斷的測試推進代碼的開發,從而實現既簡化代碼,又保證質量的目標。java
一看到"測試先行"、"測試驅動"這樣的名字,就深深地激起了我強烈的好奇心,開始了本身的探索之旅..程序員
心靈震憾編程
一段時間的學習,讓個人心裏受到了深深的震撼。咱們原來的方法竟然如此的笨我面對測試先行這一名字時,當時最大的疑問就是"程序都尚未寫出來, 測試什麼呀!"。後來一想,其實這是一個泥瓦匠都明白的道理,倒是本身在畫地爲牢。咱們來看看兩個不一樣泥瓦匠是數據結構
如何工做的吧:框架
工匠一:先拉上一根水平線,砌每一塊磚時,都與這根水平線進行比較,使得每一塊磚都保持水平。ide
工匠二:先將一排磚都砌完,而後拉上一根水平線,看看哪些磚有問題,再進行調整。工具
你會選擇哪一種工做方法呢?你必定會罵工匠二笨吧!這樣多浪費時間呀! 然而你本身想一想,你平時在編寫程序的時候又是怎麼作的呢?咱們就是按工匠二的方法在幹活的呀!甚至有時候比工匠二還笨,是整面牆都砌完了,直接進行"集成測試",常常讓整面的牆倒塌。看到這裏,你還以爲本身的方法高明嗎?單元測試
單元測試長期以來被忽視學習
每個程序員都知道應該爲本身的代碼編寫測試程序,但卻不多這樣作。當人們問爲何的時候,最常聽到的回答就是:"咱們的開發工做太緊張了"。但這樣卻致使了一個惡性循環,越是沒空編寫測試程序,代碼的效率與質量越差,花在找Bug、解決Bug的時間也愈來愈多, 實際效率大大下降。因爲效率下降了,所以時間更緊張,壓力更大。你想一想,爲何不拉上一根水平線呢?難道,咱們不可以將後面浪費的時間花在單元測試上,使得咱們的程序一開始就更加健壯,更加易於修改嗎?拋棄原來的託詞吧!測試
咱們的自動化水平過低了
有人還會解釋說,那是由於拉根水平線很簡單,而寫測試程序倒是十分複雜的。我暫且對這句話自己不置能否。不過也體現了一個新問題,咱們須要更加方便、省時的編寫測試程序的方法。
要測試一個類,最簡單的方法是直接在調試器中使用表達式觀察對象的值與狀態,你也能夠在程序中加上一些斷言、打印中間值等,固然還能夠編寫專門的測試程序。可是這些方法都有一個很大的侷限性,都須要加入人工的判斷和分析。
由此,自動化測試的引入纔是解決之道。正是由於如此,提倡"測試驅動開發"的人羣,開發出一系列的自動化單元測試框架xUnit,如今已經有針對Java、Pyhton、C++、PHP等各類經常使用語言的測試框架。這足以搪塞住那些以"編寫測試代碼太麻煩"爲理由的開發人員,讓他們沒有理由逃避單元測試。
正如Robert Martin所說:"測試套件運行起來越簡單,就會越頻繁地運行它們。測試運行越多,就會越快地發現和那些測試的任何背離。若是可以一天屢次地運行全部的測試,那麼系統的失效時間就決不會超過幾分鐘"。
認清測試驅動開發
測試驅動開發理論最初源於對這些問題的思考:
1)若是咱們可以在編寫程序代碼以前先進行測試方案的設計,會怎樣?
2)若是咱們保證除非沒有這個功能將致使測試失敗,不然就不在程序中實現該功能,會怎樣?
3)換一個角度,若是當測試時發現必須增長某項功能纔可以經過測試時, 咱們就增長這一功能,會怎樣?
大師們經過帶着這些問題的實踐, 發現這的確是一個提升軟件代碼質量, 使得效率獲得保障的一個很好出發點。
以這樣的思路進行軟件開發,能夠保證程序中的每一項功能都有測試來驗證它是正確的,並且每當功能被無心修改時, 測試程序會發現。同時,也使咱們得到了一個新的觀察點,從對程序調用者有利的視角來觀察咱們的程序,這使得咱們在關心程序功能的自己還可以對接口予以足夠感悟測試驅動開發的關注,使得其更容易被調用。另外,這種思路下的代碼,將變得更加易於調用,也就必須使其與其它代碼保持低耦合性。而且,當你想複用這些模塊時,測試代碼給出了很好的示例。這一切,使得軟件開發工做的質量一會兒變得有保障了。
所以,測試驅動開發的精髓在於: 將測試方案設計工做提早,在編寫代碼以前先作這一項工做; 從測試的角度來驗證設計,推導設計; 同時將測試方案看成行爲的準繩,有效地利用其檢驗代碼編寫的每一步,實時驗證其正確性,實現軟件開發過程的"小步快走"。
實踐測試驅動開發
下面,我就結合一個實際的小例子,來講明如何進行"測試驅動開發"。本實例在J2SE SDK 1.4.2環境下開發,以及配套工具JUnit 3.8.1。
任務簡述
隊列是一種在程序開發中十分經常使用的數據結構,在此我就以編寫一個實現隊列功能的類--Queue爲例進行說明。該類將實現如下基本運算:
判斷隊列是否爲空:empty()
插入隊列(即在隊列未尾增長一個數據元素):inqueue(x)
出隊列(也就是將隊列首數據元素刪除):outqueue()
取列頭(也就是讀者隊列首數據元素的值):gethead()
清空隊列(也就是將隊列的全部數據元素全刪除): clear()
查詢x在隊列中的位置:search(x)
測試案例分析
在測試驅動開發實踐中,第一步就是考慮測試方案,經過分析該類的功能,咱們能夠獲得如下測試案例:
1) 隊列爲空測試
TC01: 隊列新建時,應爲空;
TC02: 清空隊列後,應爲空;
TC03: 當出隊列操做次數與插入隊列操做次數同樣時,應爲空;
2) 插入隊列測試:
TC04: 插入隊列操做後,新數據元素將插入在隊列的未尾;
TC05: 插入隊列操做後,隊列將必定不爲空;
3) 出隊列測試
TC06: 出隊列操做後,第一個數據元素將被從隊列中刪除;
4) 取隊頭測試
TC07: 取隊頭操做將得到隊列中的第一個數據元素。
5) 清空隊列測試
TC08: 清空隊列操做後,隊列將爲空隊列;
注: 此處爲了講解的方便,並未將全部的測試用例都列出,同時也選擇了一些十分簡單的測試用例。
第一次迭代
咱們首先編寫第一個測試代碼,這一測試代碼只考慮了測試案例TC01, 也就是保證新建的隊列爲空:
import junit.framework.*;
//每一個使用JUnit編寫的測試代碼都應該包括本行
public class testQueue extends TestCase
//建立一個測試用例,繼承TestCase
{
protected Queue q1;
public static void main (String[] args)
{
junit.textui.TestRunner.run (suite());
//執行測試用例
}
protected void setUp() //環境變量準備
{
q1= new Queue();
}
public static Test suite() //通用格式,指定測試內容
{
return new TestSuite(testQueue.class);
}
public void testEmpty() //如下每一個方法就是一個測試
{
assertTrue(q1.empty());
//當隊列新建時,應爲空-TC01
}
}
安裝JUnit十分簡單,只需在www.junit.org中下載最新的軟件包(ZIP格式), 而後將其解壓縮,而且將"JUnit安裝目錄\junit.jar" 以及"JUnit安裝目錄"都加到系統環境變量CLASSPATH中去便可。
執行套件能夠像上述程序同樣在main方法中使用,也能夠直接在命令行調用:java junit.textui.TestRunner 測試類名(文本格式)、java junit.awtui.TestRunner 測試類名(圖形格式,AWT版)、java junit.swingui.TestRunner測試類名(圖形版,Swing版)。
編譯執行(即在命令行執行javac testQueue.java和javatestQueue), 你會發現屏幕上出現提示:
.E 一個小點說明執行了一個測試用例,E表示其失敗
Time: 0.11 說明執行測試共花費了0.11秒
There was 1 error: 說明存在一個錯誤
1) testEmpty(testQueue)java.lang.NoClassDefFoundError: Queue
at testQueue.setUp(testQueue.java:13)
at testQueue.main(testQueue.java:9)
FAILURES!!!
Tests run: 1, Failures: 0, Errors: 1
測試沒有經過是確定的,由於Queue類都尚未寫呢?怎麼可能經過測試,所以,咱們就編寫如下代碼,以使測試經過:
public class Queue extends java.util.Vector
{
public Queue()
{
super();
}
public boolean empty()
{
return super.isEmpty();
}
}
將這個類編譯後,再次執行測試程序,這時將出如下提示:
. 一個小點說明執行了一個測試用例,沒有E表示其成功
Time: 0.11
OK (1 test)
你還可使用前面咱們說到的另兩個命令,使測試反饋以圖形化的形式體現出來,例如,執行java junit.awtui.TestRunner testQueue, 將出現:
圖1
第二次迭代
接下來,咱們修改測試程序,加入測試案例TC0四、TC05的考慮。
import junit.framework.*;
public class testQueue extends TestCase
{
protected Queue q1,q2;
public static void main (String[] args)
{
junit.textui.TestRunner.run (suite());
}
protected void setUp() {
q1= new Queue();
q2= new Queue();
q2.inqueue("first"); /對隊列q2執行插入隊列操做
q2.inqueue("second");
}
public static Test suite()
{
return new TestSuite(testQueue.class);
}
public void testEmpty()
{
assertTrue(q1.empty());
//當隊列新建時,應爲空-TC01
}
public void testInqueue()
{
assertTrue(!(q2.empty()));
//執行了插入隊列操做,隊列就應不爲空-TC05
assertEquals(1,q2.search("second"));
//search方法用於肯定元素在隊列中的位置
//後插入的數據元素,應在未尾-TC04
//插入兩個,第一個在位置0, 第二在位置1
}
}
根據這個測試代碼,咱們須要在Queue類中添加上inqueue() 和search() 兩個方法,以下所示:
public class Queue extends java.util.Vector
{
public Queue()
{
super();
}
public boolean empty()
{
return super.isEmpty();
}
public synchronized void inqueue (Object x)
{
super.addElement(x);
}
public int search(Object x)
{
return super.indexOf(x);
}
}
編譯以後,再次執行java junit.awtui.TestRunnertestQueue, 你將再次看到成功的綠色。
圖2
咱們仔細看一下這一界面。
1) 最上面列出了測試代碼的類名,右邊有一個"Run" 按鈕,當你須要再次運行這一測試代碼時,只需單擊這個按鈕。另外,將"Reload classesevery run" 選項打上勾頗有用,當你測試未經過(出現紅色時), 你能夠轉身去修改代碼,修改完後,只需再按"Run" 按鈕就能夠再次運行。
2) 中間區域是一個狀態彙報區,紅色表示未經過,統計了共運行了多少個測試(也就是在TestCase類中方法的數量)。
3) 若是測試時出現錯誤,例如,咱們不當心將"assertTrue(!(q2.empty()));" 誤寫成爲"assertTrue(q2.empty());" 就將形成測試失敗:
注:因爲第一個測試仍是經過的,所以你會看到綠色條一閃。這時,你將會發現JUnit會將錯誤列出來,而且對應的"Run"按鈕也由灰變成了亮,這表示你能夠轉身修改,完成後單擊這個"Run按鈕"能夠只作剛纔失效的這個測試,這將節省大量的時間。
同時,在最下面的窗體裏,列出了失效的詳細緣由。
後面的迭代
到這裏,開發尚未完成,但這種思想卻已經經過這樣兩個短小的實踐傳遞出去了,後面的活你們能夠動手試一下。
另外值得一提的是,這裏雖然洋洋灑灑一大篇,實際兩次迭代花費了我不到15分鐘就完成了。並且,當看到綠條時,內心十分舒暢。
一些遺憾
文章到此就告一段落,但卻有些許遺憾。
遺憾之一:這只是一篇文章,沒有辦法把全部方面都講得面面俱到,以至於你們可能沒法立刻上手。
正是因爲這樣的緣由,本文取名爲"感悟", 與你們交流一下體會,但願可以幫助你們更好地接受"測試驅動開發"的理念,並開始着手實踐。
遺憾之二:筆者水平有限,沒法解決你們的各類問題。
讓筆者感到欣慰的是,記載着這些答案的《測試驅動開發》、《敏捷軟件開發》、《擁抱變化: 解析極限編程》等大做都已悉數擺上了中國的書店。路雖難走,但明師已有。
實踐永遠是學習的最好方法,看到筆者的感悟,就開始極限之旅吧,由於那裏風光無限,樂趣無限。當你掌握了測試驅動開發的精髓,那你就可以對你本身編寫的全部代碼充滿信心,再也不擔憂它們何時在你的後面放一冷箭,今後告別這給你帶來無限壓力的苦惱。