軟體工程與大型整合專案—以WiMAX整合型計畫為例 (2/3)
版本控管
在整合國科會CMMI文件與程式開發的過程中,總計劃與各子計畫的文件與程式都需要一個版本管理的機制,一方面萬一有電腦當機或是檔案損毀時,可以迅速還原最新的版本,解決資料備份的問題,另一方面還可以在歷史紀錄中往返,將刪去的文件或程式找回來。對於文件或程式整合而言,版本控管十足是一個Time Machine。
我們在第一年開發之前就已經預期到版本控管的重要性,因此在專案初期便架設可透過Web介面存取的版本控管伺服器(Apache + Subversion),並且安排Subversion的教育訓練。但是,可能是一開始時教育訓練的內容安排不符合需求,也可能是開發團隊不夠用心,導致版本控管並不上軌道。最常發生的問題包括:團隊成員未確實解決衝突(Conflicts)、久久不送交(Commit)程式、送交無法編譯的程式、送交時不寫註解等。
這些問題的原因,有部分與先前討論過的缺乏一致的開發環境有關,部分團隊使用的IDE沒有支援版本控管,所以得在IDE編譯完後才知道有沒有問題,然後再使用其他版本控管軟體(TortoiseSVN等)送交程式;或在Windows上取出(Check out)程式然後複製到Linux的機器上修改與編譯,然後再複製回Windows上覆蓋舊版本後送交,然而在取出後到送交前的這段時間,如果沒有進行更新(Update)和合併(Merge)的動作,這些行為常常會導致衝突。
久而久之,有些團隊成員開始害怕使用版本控管系統,導致送交的週期越來越長,甚至有超過二個禮拜沒有送交程式碼,失去使用版本控管的優點。長時間的修改送交週期,也讓成員忘記曾經修改過什麼東西,自然不知道要寫什麼註解,這些都是讓版本控管失去威力的錯誤行為。經過一整年的觀察後,總計畫在第二年開始前,重新規劃Subversion的使用方式。
首先,我們將版本控管的支援加入一致的開發環境中。在Virtual Machine中,我們使用Eclipse搭配Subclipse套件,讓IDE與版本控管合而為一,之前也提到,單元測試的執行也加到程式中,這些都是確保程式無誤的前置作業。另外,針對文件撰寫的環境(通常是Windows平台),撰寫TortoiseSVN的操作手冊,讓新加入的成員快速進入狀況。
接著,我們訂定幾個需要遵守的原則(Principle),在文件部分,要進行編輯的文件一定要先取出最新版,然後才能進行編輯,在送交前要先檢查這段時間是否有人進行修改,由於Word之類的檔案無法自動合併,因此如果有新版本出現,則將自己的版本移出Workspace,再次取出最新版本後,將兩者的差異加到最新版本中後再送交,這段手動合併的動作必須快,避免送交前又有其他人修改。
程式原始碼在進行編輯之前一樣得取出最新版,進行編輯後送交前,須進行更新的動作,讓Subclipse自動將不同版本之間的差異合併,但這合併可能會讓程式無法編譯,因此在更新後需要進行一次完整的編譯動作,並執行過所有的單元測試,確保合併後的程式是正確無誤的,同樣的步驟需執行到Workspace中所有的檔案無衝突為止,然後盡速送交。
為了讓檔案的活性最大化,我們採取不鎖住(Lock)的原則,只有少數特例:文件內部審查完畢,進行整合的短暫期間,所有文件會被鎖住。雖然不鎖住的原則會讓衝突的機率提升,但卻允許多人同時編輯一個檔案,只要團隊成員謹守上述的原則,就可以避免衝突的發生。但是,畢竟處理衝突還是需要額外時間的,所以我們在Subversion資料夾作權限管理,子計畫在文件與程式的Repository中都有專屬的資料夾,除了總計畫外,只有子計畫的成員可以修改專屬資料夾的檔案,而其他子計畫僅能夠讀取(Read Only),以減少衝突的範圍。
另外,在重要的改版前,總計畫會將主幹(Trunk)上的程式建立分支(Branch),新的程式碼則是在分枝上進行開發,待功能比較完整且確定要將改版的功能納入時,再將分支合併到主幹上,此時在主幹上開發的子計畫則完全不受影響。例如第三年我們決定加入行動用戶端的距離計算、地圖支援等功能時,為了讓其他子計畫持續有一個完整無誤的系統可以使用,新功能都是在分支上開發,等到新功能經測試無誤後才移回主幹上與其他子計畫整合,這降低衝突跟錯誤的產生。
分支還有一個好處,由於子計畫常擔心自己的程式會導致他人的測試失敗或是編譯錯誤,建立一個新分支可以避免自己的程式影響他人,大大提高子計畫送交程式碼的意願,雖然送交的程式不見得是完整可通過所有測試的程式,卻達到一定的備份效果,避免如果子計畫電腦出問題時,所有的心血結晶化為烏有。上述種種措施,在實施半年左右,發現送交的週期有明顯地縮短,子計畫不寫註解的情況也獲得改善,不但有寫註解,內容也比過去清楚易懂。
這些原則能夠運行,除了教育訓練外,還有一個子計畫開發的輔助工具「JCIS」幫上忙(如下圖)。JCIS會定期(每日)自動到版本控管伺服器取出主幹上最新版本的程式,進行編譯然後執行單元測試,建立多種報表:測試報告、程式碼送交行數、程式碼送交頻率等,讓PM和計畫主持人監督,有了JCIS的輔助,PM可以一早打開電腦就知道有誰昨天送交的程式沒經過測試,或是誰沒有送交程式,昨天送交的程式通過多少測試、新增了多少測試以及多少測試沒有通過,及早發現問題及早進行矯正。
版本控管不只能讓程式碼共享、同時修改,最重要的是透過合併,減低使用鎖住的不便,同時是一個提升信心的工具,不但讓成員放心修改程式,在發行(Release)前更不用害怕一個修改讓程式突然壞掉,因為隨時都有一個可運行的版本在版本控管系統中。從我們的版本控管紀錄中,可以清楚發現在正式發行前,總會有多個發行候選(Release Candidate)版本,這些版本之間的差異都很小,該有的功能都已經完成,大多是提升軟體的穩定度,但我們不懼怕修改,利用版本控管來持續提升軟體品質。
作者:陳偉凱、杜秉穎
我們的Coding Standard不只要求什麼時候該換行或是什麼地方要加空白,還包含了函式命名的規則,雖然現在的IDE可以在編輯時,只打幾個字就幫忙找出正確的函式名稱,並自動完成輸入,但如果沒IDE時,一個固定的函式規則可以加快程式的編輯,如圖 2所示,我們參考Java的風格,所有的函式必須包含一個動詞,動詞放在前面或後面則有不同的意義,像被動式動詞放在函式名詞的後面表示Callback或是Event Listener。
除了上述函式、變數名稱的命名,更重要的是一整個檔案內容的結構,一個有固定順序的內容結構,讓閱讀者更快速了解程式,而且良好的順序可以加速編譯的時間,在Coding Standard的說明文件中我們採用如圖 3的方式,先給一個大的藍圖,然後再各別描述每個部份的細節,例如圖 3的第15行展開成圖 4,一層一層的說明檔案結構。
在註解方面,Java開發者都知道Java擁有完整的API說明,也知道API說明其實是使用Javadoc工具將程式碼中特定的註解過濾出來的,現在也有工具可以分析C/C++程式碼來產生API手冊,因此我們將種特定的註解(圖 5)加到Coding Standard中,希望每個開發者在寫程式的同時,也為API手冊的內容貢獻心力。
說了這麼多,但如果團隊成員不能落實,則長達13頁的Coding Standard還是和牆上的壁紙一樣沒有用,程式碼還是一樣亂七八糟,這時候計畫主持人或是專案經理人(project manager),就需透過一些機制來檢視程式碼是否符合Coding Standard。下回我們將介紹Code Review如何進行,以及Review的內容有哪些?Coding Standard只是Code Review的其中一個項目。
作者:陳偉凱、杜秉穎
Design Review and Code Review
最近輕鬆談軟工部落格有幾個跟測試與除錯有關的軟體工程諺語相當經典:
在軟體工程裡,有一個很重要的觀念是「越早發現錯誤,除錯成本越低」(這個觀念已經被廣泛應用在個人軟體程序(Personal Software Process)、單元測試(Unit Test)與測試驅動開發(Test-Driven Development等等地方)。由於Review (Design Review或Code Review)可以在程式編譯(Compile)前就開始進行,因此用測試找出錯誤的成本比用Review來得高,而且後者往往能找到許多關鍵性的問題。此外,透過團體的Design Review及Code Review,由眾人共同檢視設計或程式碼,由於每個人考慮的重點都不同,想法也各自不同,可以避免陷入盲點。
首先看Design Review,先前的幾篇文章中,我們已經談到WiMAX總計畫如何使用Use Case找出系統事件、使用Sequence Diagram與子計畫溝通介面等,而這些Sequence Diagram或是其他設計的產物,例如系統架構及Class Diagram等,都是需要經過Design Review的。一般而言,一個正式的檢閱(Formal Review)應該先準備一個Checklist,避免遺漏重要的項目,在WiMAX計畫中,我們並沒有這麼嚴格,而是先提出幾個重要的議題,例如模組的可替換性(Replacement)、程式的佈署(Deployment)、規格的擬真度和程式的可組態度(Configurability),作為Design Review的基礎。在敲定Review的時間後,總計劃與所有的子計畫主持人都一起出席,由總計畫專案經理(Project Manager)解說設計構想,如果是系統架構的Review,便說明系統被拆成幾個部分、如何佈署、如何串連、溝通等;如果是Sequence Diagram的Review,則是從System Event開始,一直展開到最細的部分。
透過Design Review,我們詳細地檢閱系統架構是否能滿足需求,有哪些地方需要修改;我們也以同樣的方式檢閱Sequence Diagram,找出任何設計時可能忽略的盲點,以及整合時必須注意的事項。照我們過去幾年的經驗,總計畫每年主持的Design Review有四至五次,當所有主持人都參與Design Review時,每次都會發現一些議題、釐清一些觀念、調整部分設計,使得設計漸趨嚴謹,最終將設計定案。當然,各子系統的實作都是在確認設計沒問題後才開始進行的。
開始實作以後,各子系統的細部設計或是演算方式也是以類似的方式進行Code Review:由程式開發者逐行講解程式流程,各別子計畫的主持人及其他程式開發者聆聽。雖然我們並未採用特定的Checklist,但是,由於我們有公布Coding Standard,因此特地要求子計畫主持人在進行Code Review時,針對Coding Standard所列舉的項目進行檢閱。整體而言,各子計畫產出的原始程式並沒有百分之百地遵守Coding Standard,但是,自從我們在第二年開始要求各子計劃作Code Review之後,不論產出程式的品質與Coding Standard的遵守程度,都比第一年進步很多。
至於Code Review應該多久舉行一次?一次Code Review要Review多少程式?這是交由各子計畫自行決定的,以總計畫為例,Project Manager每週都會針對當週關鍵的程式碼進行檢閱,常常一次Code Review就是四個多小時,由於模擬代理人會用到許多Thread和Socket,初期檢閱的重點在於Multithread的管理(如Race Condition)與Socket的讀寫,等到模擬代理人穩定後進入整合階段,檢閱的重點則是放在記憶體的管理,由於有許多個模組協同運作,記憶體是由誰(模組)配置(Allocation)、由誰初始化(Initialization)以及最重要的是由誰釋放(Release),這些都在設計時討論到,並取得共識,因此Review時必須檢查程式是否符合當初的共識。
在我們的計畫中,開發人員都是full-time的學生,課業壓力繁重,程式的開發常常是有一週沒一週的,如果要進行正式的Code Review,一次可能要半個多鐘頭,而且才檢閱大概100行不到的程式,對學生來說確實有困難,因此,沒有辦法對全部的code做完整的review,所以不免還是會出現一些嚴重的Bug。碰到這些狀況時,我們會立即召開臨時性的Code Review,讓有經驗的老師(子計畫主持人)負責review,有系統地檢閱相關的程式,這樣的方式在除錯上的效率確實比盲目的trial and error來的好,尤其牽扯到Multithread時,很多錯誤根本無法重製,測試常常無用武之地。
有了新武器後,雖然不能幫軟體工程師贏得全面的勝利(沒人敢說軟體沒有錯誤),但至少軟體工程師能夠更有效率地找出錯誤。之後,我們將介紹另一項武器:測試,有人可能覺得很怪,測試剛剛不是提到過效果有限嗎?即便是測試,也是有升級版,雖然還是一樣無法證明錯誤不存在,但升級版的測試武器卻更有效率!
作者:陳偉凱、杜秉穎
Unit Test
上週提到我們如何在WiMAX計畫中使用Design Review和Code Review來提升軟體的品質。除了Review,軟體測試也是非常重要的,從這週開始,我們將依序介紹在WiMAX計畫中所使用的測試技巧與工具:Unit Test、Test Driven Development及Continuous Integration。
一般學生(或程式設計師)最常見的測試與除錯流程可以歸納成幾個步驟:(1)操作時發現程式出問題,可能是程式當掉(Crash),或是結果不合乎預期;(2)為了找出錯誤,開始在可能的執行路徑(Execution Path)上加上許多printf;(3)重複地執行程式,加入更多printf並觀察輸出的結果;(4)找到錯誤後,把之前加入的所有printf移除。
這個流程乍看之下沒什麼問題,但是,當發生其他問題時,同樣的步驟得再進行一次,由於無法確定問題在哪裡,許多不相關的檔案因此被加入printf,如果不移除printf的話,會變成版本控管中檔案的變異,但移除了又無法在之後的測試或除錯中重複利用。比較有技巧的寫法可能用C語言的前置處理器(#ifdef)來包裝printf,但這卻大幅降低程式的可讀性,而且當printf一多,畫面不斷捲動,不但很難觀察輸出的正確性,而且人工觀察也很沒效率。
回顧這整個流程,為什麼我們需要加入printf呢?因為printf可以將結果顯示在螢幕上,讓我們用肉眼檢查結果的正確性。那麼,又為什麼要在執行路徑上加入大量的printf呢?原因很簡單:當bug出現時,我們想縮小範圍,檢視某個小單元的code是否正確,而為了執行這個單元,我們必須引導整個程式執行到一個特定的路徑,才能抵達這個單元,並且知道其輸入與預期的輸出。然而,既然是檢查某個小單元的正確性,套用Unit Testing Framework讓電腦來做檢查才是最方便的,畢竟當資料量一大,肉眼比對很容易出錯,而且,由於測試程式碼(Test Code)獨立於產品程式碼(Production Code)之外,測試碼也不會危害程式碼的可讀性。
更重要的是,測試程式碼可以重複被執行,每個小單位的程式碼只要被修改,都可以立刻執行對應的測試程式碼,檢查修改是否真的解決錯誤、會不會衍生其他錯誤。由於Unit Testing Framework並沒有限制程式單位的大小,待測程式的對象可以是一個函式、一個類別、一個Scenario或是一個Use Case,因此,我們鼓勵學生寫Unit Test,而且當問題發生時,不是在程式碼中加入printf,而是將一整個導致問題發生的流程寫成一個Unit Test,不但不會破壞產品程式碼,還可以持續增加Unit Test的數量。
舉WiMAX計畫的例子來說,在WiMAX的協定中,QoS (Quality of Service)是相當複雜的一塊,而且隨著環境不同,其封包排程(Scheduling)的順序也會不同,因此QoS的開發者常常會利用printf將許多環境變數與排程結果顯示在畫面上,造成畫面不斷捲動,令人眼花撩亂。這個現象在導入Unit Test後得到改善(CppUnit),QoS團隊將不同的環境變數所需的測試改寫為數個Unit Test,例如請求連線的函式,每當有一個不曾建立連線的程式,其封包進入WiMAX網路中,辨識連線是否存在的函式便會回傳true,此時請求連線,如果建立連線成功,當同個程式的封包再次進入WiMAX網路,辨識連線是否存在的函式便回傳false,如圖 1所示,不同的環境及條件可以寫成多個測試,以驗證請求連線的演算法是否正確。
同樣地,我們也利用Unit Testing Framework進行整合測試,這對於需要整合多個子計畫的總計畫來說非常重要,就如同之前觀察的測試流程,我們將WiMAX模擬的流程寫成一個Test Method,然後將printf用CPPUNIT_ASSERT取代,用以檢查每個子計畫的輸出是否正確,同樣地,不同的封包(應用程式)類型、封包的頻率等等,都可以用不同的Test Method不斷地進行測試,一但發現錯誤時,也可以更容易找到問題在哪裡。
在制定開發環境時,我們將Unit Test的啟動加入執行檔中,因此只要在執行時指定參數(wimax –test),程式便會自動執行所有的Unit Test,方便我們驗證是否所有的Unit Test都能正確執行。當Unit Test累積到一定數量時,執行所有的測試是蠻費時的,為了節省測試時間,子計畫在開發時也可以透過額外的參數,限定執行測試的範圍,例如:wimax –test MAC PHY1,就僅執行MAC和PHY1子計畫的測試,縮短測試所需的時間。
我們把自動化單元測試視為一種升級版的軟體測試。Unit Test雖然會帶來一些額外的負擔,但是長遠來看,對計畫的執行是有幫助的。舉例說Unit Test是需要維護的,每當程式邏輯或流程改變時,對應的測試便得進行修改,因此維護Unit Test多少會影響程式開發的進度,但是,每當需要維護時,就表示Unit Test確實有找出程式邏輯或是流程的改變,而當重複執行的次數越多時,平均的測試成本就會下降,因此是很值得做的。
就整體Unit Test的「量」而言,我們的計畫中並沒有做到所有的Method都有對應的Unit Test,尤其是各個子計畫間對於Unit Test的標準差距頗大,這是很可惜的地方,不過在時間與資源有限的情況下,我們已經盡力要求子計畫多做Unit Test。事實上,子計畫充分的測試是整個計劃整合最重要的關鍵,在我們的經驗中,每年計劃整合時期都會出現幾個很難抓的bug,耗費無數debug的時間,然而最後還是發現這些bug其實都是子計畫可以在Unit Test階段就先找出來的,這也更驗證了Unit Test的重要性。
作者:陳偉凱、杜秉穎
Test Driven Development
上一次談到單元測試與其重要性,如果單元測試能落實的話,對於提升軟體品質與降低除錯成本確實都有很大的幫助。然而,當開發團隊人力有限時,在時程壓力下,第一個被犧牲的往往就是單元測試,此時,開發者通常等程式開發到一定的程度(Workable)後,針對Happy Path操作程式,透過GUI的顯示(或使用printf)檢查程式是否正確,若發現錯誤,則馬上進行除錯的動作。這樣流程除了測試無法重複利用外,最大的問題是程式品質常常被犧牲了,而且,Happy Path以外的狀況往往被忽略,沒有確實處理!結果是bug修理不完、壓力不減,進度還是趕不上。
WiMAX第一年的開發經驗大體上就是如此。雖然我們知道單元測試的重要性,但是,開發程式已經耗去大部分的時間,到了快結案時,又因為總計畫還沒有Workable的版本,無法確定已經開發的子計畫模組是否是正確的,等到總計畫與子計畫程式開始整合與測試時,所剩的時間已經不多了,於是只好壓縮測試的數量,結果是越接近結案期限時,整個團隊的壓力越大,一些介面或是程式的問題,不斷在整合和測試時冒出來,此時除錯的成本(時數)大到難以估計,總計畫團隊成員不得不長期加班,甚至睡在學校,勉強完成整合,但是,我們對於產出的軟體品質還是不滿意,更別說要做更充分的測試了。
第二年起,我們開始嘗試測試驅動開發(Test Driven Development; TDD),希望藉由TDD改善第一年面臨的困境。TDD可以減少開發者在時程壓力下跳過測試的現象,TDD的開發方式如下圖所示,在開發一個新的功能前,就先撰寫該功能的自動化測試案例,此時,測試當然一定會失敗,於是得到一個紅燈,接著才開發程式,先用最快(Quick)的方式讓號誌燈變成綠燈,表示程式是正確的(但可能是Dirty的程式),然後,在測試的保護傘下,開始進行重構(Refactor),讓程式變乾淨、漂亮,重構期間只要做任何修改馬上就執行所有的測試,如果有任何測試失敗,趕緊修復,使得重構期間一直保持綠燈,直至重構完畢。下一個功能的開發,一樣先撰寫測試案例,然後才開發,這就是所謂的測試驅動開發。
那麼為什麼TDD可以減少壓力呢?如果不使用TDD的話,開發者在設計程式時通常不會仔細考慮如何自動測試該程式,因此往往在程式開發完成想要撰寫單元測試時,才發現程式的可測試度(Testability)很低,難以測試,不得不再花時間修改程式的介面,提高其可測試度,結果反而增加時程的壓力,要不就是在時程壓力下,乾脆放棄測試了,而這兩者都是不利的。反之,若是採用TDD,則開發者在設計程式前就必須考慮該程式應該提供什麼樣的介面與測試,如此,自然提高程式的可測試度,又TDD如果落實的話,所有的功能都一定有對應的測試案例,因此,當時程壓力大時,開發人員不會因為時間不夠而放棄測試(測試可以自動執行),自然能提升軟體的品質。
第二年的WiMAX計畫在敲定分散式架構後,我們就開始考慮如何測試?例如:模擬代理人(Agent)如何在沒有主控台(Console)的情況下進行測試?如何在單機上執行多個模擬代理人減少(部分)測試所需的電腦數量?子計畫的開發進度不同,如何繞過(Bypass)部分模組進行系統測試?為了解決這些測試上的問題,我們調整原本的設計,例如:加入Script的支援,使得模擬代理人可以直接以Script啟動並指定角色;打斷模擬代理人與主控台的強烈偶合(Coupling);另外,在啟動模擬代理人時,可以指定通訊埠(TCP Port),使得同一台機器可以同時模擬多個代理人程序(Process),以便進行狀態轉換的測試;在Bypass模組的部分,我們使用了幾個設計樣式(Design Pattern),讓程式能在執行時(Runtime)抽換模組,不需要結束程式再重新啟動。這些設計上的改變,都是因為導入TDD後,在開發前便考慮測試,使得WiMAX模擬系統的可測試度提升了不少。
測試驅動開發除了跟Unit Test搭配,也能和Design Review與Code Review搭配使用。在WiMAX總計畫中,所有針對測試所加入的設計,或是測試案例,都會經由Design Review確保沒有背離需求規格,當測試案例被寫成測試程式碼後,透過Code Review確保測試條件都符合測試案例的規範,驗證(Assertion)是否滿足測試案例,不然當測試結果是錯的,卻不知道是測試寫錯了還是程式寫錯了,反而更麻煩。雖然Design Review和Code Review不能保證測試程式碼是百分之百正確的,但測試程式碼通常不含複雜的邏輯,在Review後要出錯的機率遠比產品程式碼出錯的機率低。
比較可惜的是TDD和Unit Test一樣,在有限時間與資源下,並沒有辦法全面導入子計畫中。在我們總計畫負責的部份確實花了不少心力進行TDD,盡可能讓程式的Testability提高,並維持測試的質與量。就量來說,總計畫的數百個自動化測試中,在開發之前完成的約有30%,其中大多是依據設計規格所撰寫的整合測試(Integration Test)或驗收測試(Acceptance Test),剩餘的70%則為針對Class或小模組所撰寫的單元測試,而這些測試在後期的整合階段幫了許多忙,使得總計畫的開發人員不用擔心在加入子系統 (整合進新的程式)時,新的程式會影響整個系統,雖然測試數量不算很多,但是,每當跑過這數百個測試,還是能有一定程度的信心。
當自動化測試的數量達到一定程度後,測試所需的時間不免會逐漸拉長。例如WiMAX總計畫的所有測試執行一遍大約需要15分鐘,如果開發者每修改一小部分程式就得等上15分鐘的測試時間,似乎又有點不切實際,所以,我們在開發過程中,只執行那些和修改有關的測試,等到要送交(Commit)程式碼至Subversion伺服器時,才執行全部的測試(通過才能送交),以減少測試執行的時間。下一次我們將介紹WiMAX如何透過持續整合(Continuous Integration)工具,將所有的工作(開發、整合與測試)兜在一起。
作者:陳偉凱、杜秉穎
軟體工程與大型整合專案—以WiMAX整合型計畫為例 (1/3)
在整合國科會CMMI文件與程式開發的過程中,總計劃與各子計畫的文件與程式都需要一個版本管理的機制,一方面萬一有電腦當機或是檔案損毀時,可以迅速還原最新的版本,解決資料備份的問題,另一方面還可以在歷史紀錄中往返,將刪去的文件或程式找回來。對於文件或程式整合而言,版本控管十足是一個Time Machine。
我們在第一年開發之前就已經預期到版本控管的重要性,因此在專案初期便架設可透過Web介面存取的版本控管伺服器(Apache + Subversion),並且安排Subversion的教育訓練。但是,可能是一開始時教育訓練的內容安排不符合需求,也可能是開發團隊不夠用心,導致版本控管並不上軌道。最常發生的問題包括:團隊成員未確實解決衝突(Conflicts)、久久不送交(Commit)程式、送交無法編譯的程式、送交時不寫註解等。
這些問題的原因,有部分與先前討論過的缺乏一致的開發環境有關,部分團隊使用的IDE沒有支援版本控管,所以得在IDE編譯完後才知道有沒有問題,然後再使用其他版本控管軟體(TortoiseSVN等)送交程式;或在Windows上取出(Check out)程式然後複製到Linux的機器上修改與編譯,然後再複製回Windows上覆蓋舊版本後送交,然而在取出後到送交前的這段時間,如果沒有進行更新(Update)和合併(Merge)的動作,這些行為常常會導致衝突。
久而久之,有些團隊成員開始害怕使用版本控管系統,導致送交的週期越來越長,甚至有超過二個禮拜沒有送交程式碼,失去使用版本控管的優點。長時間的修改送交週期,也讓成員忘記曾經修改過什麼東西,自然不知道要寫什麼註解,這些都是讓版本控管失去威力的錯誤行為。經過一整年的觀察後,總計畫在第二年開始前,重新規劃Subversion的使用方式。
首先,我們將版本控管的支援加入一致的開發環境中。在Virtual Machine中,我們使用Eclipse搭配Subclipse套件,讓IDE與版本控管合而為一,之前也提到,單元測試的執行也加到程式中,這些都是確保程式無誤的前置作業。另外,針對文件撰寫的環境(通常是Windows平台),撰寫TortoiseSVN的操作手冊,讓新加入的成員快速進入狀況。
接著,我們訂定幾個需要遵守的原則(Principle),在文件部分,要進行編輯的文件一定要先取出最新版,然後才能進行編輯,在送交前要先檢查這段時間是否有人進行修改,由於Word之類的檔案無法自動合併,因此如果有新版本出現,則將自己的版本移出Workspace,再次取出最新版本後,將兩者的差異加到最新版本中後再送交,這段手動合併的動作必須快,避免送交前又有其他人修改。
程式原始碼在進行編輯之前一樣得取出最新版,進行編輯後送交前,須進行更新的動作,讓Subclipse自動將不同版本之間的差異合併,但這合併可能會讓程式無法編譯,因此在更新後需要進行一次完整的編譯動作,並執行過所有的單元測試,確保合併後的程式是正確無誤的,同樣的步驟需執行到Workspace中所有的檔案無衝突為止,然後盡速送交。
為了讓檔案的活性最大化,我們採取不鎖住(Lock)的原則,只有少數特例:文件內部審查完畢,進行整合的短暫期間,所有文件會被鎖住。雖然不鎖住的原則會讓衝突的機率提升,但卻允許多人同時編輯一個檔案,只要團隊成員謹守上述的原則,就可以避免衝突的發生。但是,畢竟處理衝突還是需要額外時間的,所以我們在Subversion資料夾作權限管理,子計畫在文件與程式的Repository中都有專屬的資料夾,除了總計畫外,只有子計畫的成員可以修改專屬資料夾的檔案,而其他子計畫僅能夠讀取(Read Only),以減少衝突的範圍。
另外,在重要的改版前,總計畫會將主幹(Trunk)上的程式建立分支(Branch),新的程式碼則是在分枝上進行開發,待功能比較完整且確定要將改版的功能納入時,再將分支合併到主幹上,此時在主幹上開發的子計畫則完全不受影響。例如第三年我們決定加入行動用戶端的距離計算、地圖支援等功能時,為了讓其他子計畫持續有一個完整無誤的系統可以使用,新功能都是在分支上開發,等到新功能經測試無誤後才移回主幹上與其他子計畫整合,這降低衝突跟錯誤的產生。
分支還有一個好處,由於子計畫常擔心自己的程式會導致他人的測試失敗或是編譯錯誤,建立一個新分支可以避免自己的程式影響他人,大大提高子計畫送交程式碼的意願,雖然送交的程式不見得是完整可通過所有測試的程式,卻達到一定的備份效果,避免如果子計畫電腦出問題時,所有的心血結晶化為烏有。上述種種措施,在實施半年左右,發現送交的週期有明顯地縮短,子計畫不寫註解的情況也獲得改善,不但有寫註解,內容也比過去清楚易懂。
這些原則能夠運行,除了教育訓練外,還有一個子計畫開發的輔助工具「JCIS」幫上忙(如下圖)。JCIS會定期(每日)自動到版本控管伺服器取出主幹上最新版本的程式,進行編譯然後執行單元測試,建立多種報表:測試報告、程式碼送交行數、程式碼送交頻率等,讓PM和計畫主持人監督,有了JCIS的輔助,PM可以一早打開電腦就知道有誰昨天送交的程式沒經過測試,或是誰沒有送交程式,昨天送交的程式通過多少測試、新增了多少測試以及多少測試沒有通過,及早發現問題及早進行矯正。
版本控管不只能讓程式碼共享、同時修改,最重要的是透過合併,減低使用鎖住的不便,同時是一個提升信心的工具,不但讓成員放心修改程式,在發行(Release)前更不用害怕一個修改讓程式突然壞掉,因為隨時都有一個可運行的版本在版本控管系統中。從我們的版本控管紀錄中,可以清楚發現在正式發行前,總會有多個發行候選(Release Candidate)版本,這些版本之間的差異都很小,該有的功能都已經完成,大多是提升軟體的穩定度,但我們不懼怕修改,利用版本控管來持續提升軟體品質。
作者:陳偉凱、杜秉穎
Coding Standard
先前談了許多設計,但設計終究需要透過實作成為成品,對一個軟體專案來說,實作的重點就是寫程式,那麼在開始寫程式前,還需要什麼準備工作嗎?有的,就是制定一個共同的Coding Standard。在過去多年的教學經驗中,只要是程式設計相關的課程,我都會將Coding Standard納入評分,並要求助教在批改作業時注意程式的Coding style。雖然我並不要求學生使用特定的Coding Standard,但學生必須自行選擇固定的命名、空格、縮排、…等法則,並且遵守選擇的法則,剛開始時學生多少會有點不耐,但長時間後,寫出來的程式自然就整齊許多,學生自己也能感受到好處。
WiMAX是一團隊計畫,成員來自不同領域,所受的訓練也不同,如果沒有一個共同的Coding Standard,當所有程式整合在一起會多麼不協調?就好像把伊斯蘭建築的圓頂加到長城的城樓上。或許不協調只是美觀上的問題,但Coding Standard不只是美觀上的問題,一致的Coding Standard更能加速別人了解程式,進而提高效率。
由於我們的程式使用Java和C/C++兩種不同語言寫成,我們參考了GNU Coding Standard和Java Coding Style Guide,最後制定出約13頁的Coding Standard。這麼長的Coding Standard看起來好像很多很難記,其實會長達13頁的原因是因為我們加入了許多例子,內容確實是條文式的規範,但除了條文式的描述,給程式例子更容易使讀者快速理解條文的意義,例如圖 1,我們根據條文將程式碼列出來,並用顏色將條文的重點標示出來。
先前談了許多設計,但設計終究需要透過實作成為成品,對一個軟體專案來說,實作的重點就是寫程式,那麼在開始寫程式前,還需要什麼準備工作嗎?有的,就是制定一個共同的Coding Standard。在過去多年的教學經驗中,只要是程式設計相關的課程,我都會將Coding Standard納入評分,並要求助教在批改作業時注意程式的Coding style。雖然我並不要求學生使用特定的Coding Standard,但學生必須自行選擇固定的命名、空格、縮排、…等法則,並且遵守選擇的法則,剛開始時學生多少會有點不耐,但長時間後,寫出來的程式自然就整齊許多,學生自己也能感受到好處。
WiMAX是一團隊計畫,成員來自不同領域,所受的訓練也不同,如果沒有一個共同的Coding Standard,當所有程式整合在一起會多麼不協調?就好像把伊斯蘭建築的圓頂加到長城的城樓上。或許不協調只是美觀上的問題,但Coding Standard不只是美觀上的問題,一致的Coding Standard更能加速別人了解程式,進而提高效率。
由於我們的程式使用Java和C/C++兩種不同語言寫成,我們參考了GNU Coding Standard和Java Coding Style Guide,最後制定出約13頁的Coding Standard。這麼長的Coding Standard看起來好像很多很難記,其實會長達13頁的原因是因為我們加入了許多例子,內容確實是條文式的規範,但除了條文式的描述,給程式例子更容易使讀者快速理解條文的意義,例如圖 1,我們根據條文將程式碼列出來,並用顏色將條文的重點標示出來。
圖 1 Coding Standard實例
我們的Coding Standard不只要求什麼時候該換行或是什麼地方要加空白,還包含了函式命名的規則,雖然現在的IDE可以在編輯時,只打幾個字就幫忙找出正確的函式名稱,並自動完成輸入,但如果沒IDE時,一個固定的函式規則可以加快程式的編輯,如圖 2所示,我們參考Java的風格,所有的函式必須包含一個動詞,動詞放在前面或後面則有不同的意義,像被動式動詞放在函式名詞的後面表示Callback或是Event Listener。
圖 2 函式名稱的規則
除了上述函式、變數名稱的命名,更重要的是一整個檔案內容的結構,一個有固定順序的內容結構,讓閱讀者更快速了解程式,而且良好的順序可以加速編譯的時間,在Coding Standard的說明文件中我們採用如圖 3的方式,先給一個大的藍圖,然後再各別描述每個部份的細節,例如圖 3的第15行展開成圖 4,一層一層的說明檔案結構。
圖 3 檔案結構與順序
圖 4 完整的類別宣告
在註解方面,Java開發者都知道Java擁有完整的API說明,也知道API說明其實是使用Javadoc工具將程式碼中特定的註解過濾出來的,現在也有工具可以分析C/C++程式碼來產生API手冊,因此我們將種特定的註解(圖 5)加到Coding Standard中,希望每個開發者在寫程式的同時,也為API手冊的內容貢獻心力。
圖 5 函式的說明
說了這麼多,但如果團隊成員不能落實,則長達13頁的Coding Standard還是和牆上的壁紙一樣沒有用,程式碼還是一樣亂七八糟,這時候計畫主持人或是專案經理人(project manager),就需透過一些機制來檢視程式碼是否符合Coding Standard。下回我們將介紹Code Review如何進行,以及Review的內容有哪些?Coding Standard只是Code Review的其中一個項目。
作者:陳偉凱、杜秉穎
Design Review and Code Review
最近輕鬆談軟工部落格有幾個跟測試與除錯有關的軟體工程諺語相當經典:
- 除三個錯就會冒出一個錯,這稱為Bug的無窮迴圈。
- 測試程式只能證明錯誤的存在,無法證明錯誤不存在。(Program testing can be used to show the presence of bugs, but never to show their absence!)
- 找程式的臭蟲真的是有夠難的,特別當你認為你的程式沒有錯的時候。(It's hard enough to find an error in your code when you're looking for it; it's even harder when you've assumed your code is error-free.)
在軟體工程裡,有一個很重要的觀念是「越早發現錯誤,除錯成本越低」(這個觀念已經被廣泛應用在個人軟體程序(Personal Software Process)、單元測試(Unit Test)與測試驅動開發(Test-Driven Development等等地方)。由於Review (Design Review或Code Review)可以在程式編譯(Compile)前就開始進行,因此用測試找出錯誤的成本比用Review來得高,而且後者往往能找到許多關鍵性的問題。此外,透過團體的Design Review及Code Review,由眾人共同檢視設計或程式碼,由於每個人考慮的重點都不同,想法也各自不同,可以避免陷入盲點。
首先看Design Review,先前的幾篇文章中,我們已經談到WiMAX總計畫如何使用Use Case找出系統事件、使用Sequence Diagram與子計畫溝通介面等,而這些Sequence Diagram或是其他設計的產物,例如系統架構及Class Diagram等,都是需要經過Design Review的。一般而言,一個正式的檢閱(Formal Review)應該先準備一個Checklist,避免遺漏重要的項目,在WiMAX計畫中,我們並沒有這麼嚴格,而是先提出幾個重要的議題,例如模組的可替換性(Replacement)、程式的佈署(Deployment)、規格的擬真度和程式的可組態度(Configurability),作為Design Review的基礎。在敲定Review的時間後,總計劃與所有的子計畫主持人都一起出席,由總計畫專案經理(Project Manager)解說設計構想,如果是系統架構的Review,便說明系統被拆成幾個部分、如何佈署、如何串連、溝通等;如果是Sequence Diagram的Review,則是從System Event開始,一直展開到最細的部分。
透過Design Review,我們詳細地檢閱系統架構是否能滿足需求,有哪些地方需要修改;我們也以同樣的方式檢閱Sequence Diagram,找出任何設計時可能忽略的盲點,以及整合時必須注意的事項。照我們過去幾年的經驗,總計畫每年主持的Design Review有四至五次,當所有主持人都參與Design Review時,每次都會發現一些議題、釐清一些觀念、調整部分設計,使得設計漸趨嚴謹,最終將設計定案。當然,各子系統的實作都是在確認設計沒問題後才開始進行的。
開始實作以後,各子系統的細部設計或是演算方式也是以類似的方式進行Code Review:由程式開發者逐行講解程式流程,各別子計畫的主持人及其他程式開發者聆聽。雖然我們並未採用特定的Checklist,但是,由於我們有公布Coding Standard,因此特地要求子計畫主持人在進行Code Review時,針對Coding Standard所列舉的項目進行檢閱。整體而言,各子計畫產出的原始程式並沒有百分之百地遵守Coding Standard,但是,自從我們在第二年開始要求各子計劃作Code Review之後,不論產出程式的品質與Coding Standard的遵守程度,都比第一年進步很多。
至於Code Review應該多久舉行一次?一次Code Review要Review多少程式?這是交由各子計畫自行決定的,以總計畫為例,Project Manager每週都會針對當週關鍵的程式碼進行檢閱,常常一次Code Review就是四個多小時,由於模擬代理人會用到許多Thread和Socket,初期檢閱的重點在於Multithread的管理(如Race Condition)與Socket的讀寫,等到模擬代理人穩定後進入整合階段,檢閱的重點則是放在記憶體的管理,由於有許多個模組協同運作,記憶體是由誰(模組)配置(Allocation)、由誰初始化(Initialization)以及最重要的是由誰釋放(Release),這些都在設計時討論到,並取得共識,因此Review時必須檢查程式是否符合當初的共識。
在我們的計畫中,開發人員都是full-time的學生,課業壓力繁重,程式的開發常常是有一週沒一週的,如果要進行正式的Code Review,一次可能要半個多鐘頭,而且才檢閱大概100行不到的程式,對學生來說確實有困難,因此,沒有辦法對全部的code做完整的review,所以不免還是會出現一些嚴重的Bug。碰到這些狀況時,我們會立即召開臨時性的Code Review,讓有經驗的老師(子計畫主持人)負責review,有系統地檢閱相關的程式,這樣的方式在除錯上的效率確實比盲目的trial and error來的好,尤其牽扯到Multithread時,很多錯誤根本無法重製,測試常常無用武之地。
有了新武器後,雖然不能幫軟體工程師贏得全面的勝利(沒人敢說軟體沒有錯誤),但至少軟體工程師能夠更有效率地找出錯誤。之後,我們將介紹另一項武器:測試,有人可能覺得很怪,測試剛剛不是提到過效果有限嗎?即便是測試,也是有升級版,雖然還是一樣無法證明錯誤不存在,但升級版的測試武器卻更有效率!
作者:陳偉凱、杜秉穎
Unit Test
上週提到我們如何在WiMAX計畫中使用Design Review和Code Review來提升軟體的品質。除了Review,軟體測試也是非常重要的,從這週開始,我們將依序介紹在WiMAX計畫中所使用的測試技巧與工具:Unit Test、Test Driven Development及Continuous Integration。
一般學生(或程式設計師)最常見的測試與除錯流程可以歸納成幾個步驟:(1)操作時發現程式出問題,可能是程式當掉(Crash),或是結果不合乎預期;(2)為了找出錯誤,開始在可能的執行路徑(Execution Path)上加上許多printf;(3)重複地執行程式,加入更多printf並觀察輸出的結果;(4)找到錯誤後,把之前加入的所有printf移除。
這個流程乍看之下沒什麼問題,但是,當發生其他問題時,同樣的步驟得再進行一次,由於無法確定問題在哪裡,許多不相關的檔案因此被加入printf,如果不移除printf的話,會變成版本控管中檔案的變異,但移除了又無法在之後的測試或除錯中重複利用。比較有技巧的寫法可能用C語言的前置處理器(#ifdef)來包裝printf,但這卻大幅降低程式的可讀性,而且當printf一多,畫面不斷捲動,不但很難觀察輸出的正確性,而且人工觀察也很沒效率。
回顧這整個流程,為什麼我們需要加入printf呢?因為printf可以將結果顯示在螢幕上,讓我們用肉眼檢查結果的正確性。那麼,又為什麼要在執行路徑上加入大量的printf呢?原因很簡單:當bug出現時,我們想縮小範圍,檢視某個小單元的code是否正確,而為了執行這個單元,我們必須引導整個程式執行到一個特定的路徑,才能抵達這個單元,並且知道其輸入與預期的輸出。然而,既然是檢查某個小單元的正確性,套用Unit Testing Framework讓電腦來做檢查才是最方便的,畢竟當資料量一大,肉眼比對很容易出錯,而且,由於測試程式碼(Test Code)獨立於產品程式碼(Production Code)之外,測試碼也不會危害程式碼的可讀性。
更重要的是,測試程式碼可以重複被執行,每個小單位的程式碼只要被修改,都可以立刻執行對應的測試程式碼,檢查修改是否真的解決錯誤、會不會衍生其他錯誤。由於Unit Testing Framework並沒有限制程式單位的大小,待測程式的對象可以是一個函式、一個類別、一個Scenario或是一個Use Case,因此,我們鼓勵學生寫Unit Test,而且當問題發生時,不是在程式碼中加入printf,而是將一整個導致問題發生的流程寫成一個Unit Test,不但不會破壞產品程式碼,還可以持續增加Unit Test的數量。
舉WiMAX計畫的例子來說,在WiMAX的協定中,QoS (Quality of Service)是相當複雜的一塊,而且隨著環境不同,其封包排程(Scheduling)的順序也會不同,因此QoS的開發者常常會利用printf將許多環境變數與排程結果顯示在畫面上,造成畫面不斷捲動,令人眼花撩亂。這個現象在導入Unit Test後得到改善(CppUnit),QoS團隊將不同的環境變數所需的測試改寫為數個Unit Test,例如請求連線的函式,每當有一個不曾建立連線的程式,其封包進入WiMAX網路中,辨識連線是否存在的函式便會回傳true,此時請求連線,如果建立連線成功,當同個程式的封包再次進入WiMAX網路,辨識連線是否存在的函式便回傳false,如圖 1所示,不同的環境及條件可以寫成多個測試,以驗證請求連線的演算法是否正確。
圖1 不同條件下的請求連線
同樣地,我們也利用Unit Testing Framework進行整合測試,這對於需要整合多個子計畫的總計畫來說非常重要,就如同之前觀察的測試流程,我們將WiMAX模擬的流程寫成一個Test Method,然後將printf用CPPUNIT_ASSERT取代,用以檢查每個子計畫的輸出是否正確,同樣地,不同的封包(應用程式)類型、封包的頻率等等,都可以用不同的Test Method不斷地進行測試,一但發現錯誤時,也可以更容易找到問題在哪裡。
在制定開發環境時,我們將Unit Test的啟動加入執行檔中,因此只要在執行時指定參數(wimax –test),程式便會自動執行所有的Unit Test,方便我們驗證是否所有的Unit Test都能正確執行。當Unit Test累積到一定數量時,執行所有的測試是蠻費時的,為了節省測試時間,子計畫在開發時也可以透過額外的參數,限定執行測試的範圍,例如:wimax –test MAC PHY1,就僅執行MAC和PHY1子計畫的測試,縮短測試所需的時間。
我們把自動化單元測試視為一種升級版的軟體測試。Unit Test雖然會帶來一些額外的負擔,但是長遠來看,對計畫的執行是有幫助的。舉例說Unit Test是需要維護的,每當程式邏輯或流程改變時,對應的測試便得進行修改,因此維護Unit Test多少會影響程式開發的進度,但是,每當需要維護時,就表示Unit Test確實有找出程式邏輯或是流程的改變,而當重複執行的次數越多時,平均的測試成本就會下降,因此是很值得做的。
就整體Unit Test的「量」而言,我們的計畫中並沒有做到所有的Method都有對應的Unit Test,尤其是各個子計畫間對於Unit Test的標準差距頗大,這是很可惜的地方,不過在時間與資源有限的情況下,我們已經盡力要求子計畫多做Unit Test。事實上,子計畫充分的測試是整個計劃整合最重要的關鍵,在我們的經驗中,每年計劃整合時期都會出現幾個很難抓的bug,耗費無數debug的時間,然而最後還是發現這些bug其實都是子計畫可以在Unit Test階段就先找出來的,這也更驗證了Unit Test的重要性。
作者:陳偉凱、杜秉穎
Test Driven Development
上一次談到單元測試與其重要性,如果單元測試能落實的話,對於提升軟體品質與降低除錯成本確實都有很大的幫助。然而,當開發團隊人力有限時,在時程壓力下,第一個被犧牲的往往就是單元測試,此時,開發者通常等程式開發到一定的程度(Workable)後,針對Happy Path操作程式,透過GUI的顯示(或使用printf)檢查程式是否正確,若發現錯誤,則馬上進行除錯的動作。這樣流程除了測試無法重複利用外,最大的問題是程式品質常常被犧牲了,而且,Happy Path以外的狀況往往被忽略,沒有確實處理!結果是bug修理不完、壓力不減,進度還是趕不上。
WiMAX第一年的開發經驗大體上就是如此。雖然我們知道單元測試的重要性,但是,開發程式已經耗去大部分的時間,到了快結案時,又因為總計畫還沒有Workable的版本,無法確定已經開發的子計畫模組是否是正確的,等到總計畫與子計畫程式開始整合與測試時,所剩的時間已經不多了,於是只好壓縮測試的數量,結果是越接近結案期限時,整個團隊的壓力越大,一些介面或是程式的問題,不斷在整合和測試時冒出來,此時除錯的成本(時數)大到難以估計,總計畫團隊成員不得不長期加班,甚至睡在學校,勉強完成整合,但是,我們對於產出的軟體品質還是不滿意,更別說要做更充分的測試了。
第二年起,我們開始嘗試測試驅動開發(Test Driven Development; TDD),希望藉由TDD改善第一年面臨的困境。TDD可以減少開發者在時程壓力下跳過測試的現象,TDD的開發方式如下圖所示,在開發一個新的功能前,就先撰寫該功能的自動化測試案例,此時,測試當然一定會失敗,於是得到一個紅燈,接著才開發程式,先用最快(Quick)的方式讓號誌燈變成綠燈,表示程式是正確的(但可能是Dirty的程式),然後,在測試的保護傘下,開始進行重構(Refactor),讓程式變乾淨、漂亮,重構期間只要做任何修改馬上就執行所有的測試,如果有任何測試失敗,趕緊修復,使得重構期間一直保持綠燈,直至重構完畢。下一個功能的開發,一樣先撰寫測試案例,然後才開發,這就是所謂的測試驅動開發。
圖 1 Test Driven Development號誌燈
那麼為什麼TDD可以減少壓力呢?如果不使用TDD的話,開發者在設計程式時通常不會仔細考慮如何自動測試該程式,因此往往在程式開發完成想要撰寫單元測試時,才發現程式的可測試度(Testability)很低,難以測試,不得不再花時間修改程式的介面,提高其可測試度,結果反而增加時程的壓力,要不就是在時程壓力下,乾脆放棄測試了,而這兩者都是不利的。反之,若是採用TDD,則開發者在設計程式前就必須考慮該程式應該提供什麼樣的介面與測試,如此,自然提高程式的可測試度,又TDD如果落實的話,所有的功能都一定有對應的測試案例,因此,當時程壓力大時,開發人員不會因為時間不夠而放棄測試(測試可以自動執行),自然能提升軟體的品質。
第二年的WiMAX計畫在敲定分散式架構後,我們就開始考慮如何測試?例如:模擬代理人(Agent)如何在沒有主控台(Console)的情況下進行測試?如何在單機上執行多個模擬代理人減少(部分)測試所需的電腦數量?子計畫的開發進度不同,如何繞過(Bypass)部分模組進行系統測試?為了解決這些測試上的問題,我們調整原本的設計,例如:加入Script的支援,使得模擬代理人可以直接以Script啟動並指定角色;打斷模擬代理人與主控台的強烈偶合(Coupling);另外,在啟動模擬代理人時,可以指定通訊埠(TCP Port),使得同一台機器可以同時模擬多個代理人程序(Process),以便進行狀態轉換的測試;在Bypass模組的部分,我們使用了幾個設計樣式(Design Pattern),讓程式能在執行時(Runtime)抽換模組,不需要結束程式再重新啟動。這些設計上的改變,都是因為導入TDD後,在開發前便考慮測試,使得WiMAX模擬系統的可測試度提升了不少。
測試驅動開發除了跟Unit Test搭配,也能和Design Review與Code Review搭配使用。在WiMAX總計畫中,所有針對測試所加入的設計,或是測試案例,都會經由Design Review確保沒有背離需求規格,當測試案例被寫成測試程式碼後,透過Code Review確保測試條件都符合測試案例的規範,驗證(Assertion)是否滿足測試案例,不然當測試結果是錯的,卻不知道是測試寫錯了還是程式寫錯了,反而更麻煩。雖然Design Review和Code Review不能保證測試程式碼是百分之百正確的,但測試程式碼通常不含複雜的邏輯,在Review後要出錯的機率遠比產品程式碼出錯的機率低。
比較可惜的是TDD和Unit Test一樣,在有限時間與資源下,並沒有辦法全面導入子計畫中。在我們總計畫負責的部份確實花了不少心力進行TDD,盡可能讓程式的Testability提高,並維持測試的質與量。就量來說,總計畫的數百個自動化測試中,在開發之前完成的約有30%,其中大多是依據設計規格所撰寫的整合測試(Integration Test)或驗收測試(Acceptance Test),剩餘的70%則為針對Class或小模組所撰寫的單元測試,而這些測試在後期的整合階段幫了許多忙,使得總計畫的開發人員不用擔心在加入子系統 (整合進新的程式)時,新的程式會影響整個系統,雖然測試數量不算很多,但是,每當跑過這數百個測試,還是能有一定程度的信心。
當自動化測試的數量達到一定程度後,測試所需的時間不免會逐漸拉長。例如WiMAX總計畫的所有測試執行一遍大約需要15分鐘,如果開發者每修改一小部分程式就得等上15分鐘的測試時間,似乎又有點不切實際,所以,我們在開發過程中,只執行那些和修改有關的測試,等到要送交(Commit)程式碼至Subversion伺服器時,才執行全部的測試(通過才能送交),以減少測試執行的時間。下一次我們將介紹WiMAX如何透過持續整合(Continuous Integration)工具,將所有的工作(開發、整合與測試)兜在一起。
作者:陳偉凱、杜秉穎
軟體工程與大型整合專案—以WiMAX整合型計畫為例 (1/3)
留言
張貼留言