2007年6月7日 星期四

(2007.5月號-160期)_持續性整合開發導論(上)

在開始進入正題之前,請讀者回想一下公司目前專案開發方式,首先得有個IDE介面的開發工具(不論是商用的JBuilderWSADIDEA或著是免費的EclipseNetBeans),再者公司內多人進行協同開發,所以必須有版本控制系統(也許用古老的CVS,或目前較常用的SubVersion還是要錢的StarTeamClearCase),接者呢,討論一下工作分配後就各自開始工作了,頂多遇到問題了求教資深員工、隔幾天口頭或文件報告一下工作進度、手動輸入資料進行測試、用眼睛看看程式運作順不順暢、專案初期大家嘻皮笑臉準時下班、專案後期各個繃緊神經認命加班。最後的結論是八成以上的ProjectDelay,無法順利On Schedule,二成則靠業務或老闆的關係結案,請客戶勉強撐著用。




很熟悉嗎?在台灣如此的場景依然在無數的IT業界的各個角落上演。到底出了什麼事,未來的專案與軟體開發還是如此的循環嗎?為了追朔原因我們來簡單回顧一下軟體開發的歷史。


在過去十幾年,IT產業剛萌芽階段,對軟體開發的理解很單純就是寫程式,當時是結構化開發與設計,相對的軟體規模較小並且在崇尚個人開發技巧的狀態中完成程式的撰寫,能夠成為開發人員的都是非常令人稱羡的。由於重視個人技能且一個專案皆為一人開發,再者那時也沒有所謂的程式撰寫規範與標準,缺少有效的開發方法與軟體開發工具的支援,開發文件使用最多亦只是簡單的控制流程圖,不重視軟體開發的管理,更沒有所謂的軟體工程,因此決定軟體品質的唯一因素就是該程式開發人員的素質


時至今日,現代軟體專案不再是單打獨鬥,而是講求團隊合作,隨著資訊發達與企業成長,小型專案開發的機會少了,取而代之的是開始了為數眾多的企業級軟體專案開發,依據需要延伸出了多樣的企業級架構與觀念,包括了ERPEAIESBSCMPLMBPMSOA…等。經過幾十年經驗的累積也發展出了RUPAgileUMLDesign Patterns…等製程觀念與軟體工程理論。


但當世界都持續進步的同時,實務的軟體開發卻一直裹足不前,其原因在於開發人員多數還留有舊時代的觀念,保有一定程度的英雄主義,不擅於協同開發的方式,而大部分公司也不重視軟體工程永遠以舊思維在開發新專案與新產品,因而導致大部分的專案品質低落。


即然如此,是否能採取什麼行動來修正開發方式,妥善的管理專案的進展與狀態,追蹤程式碼的品質與提升開發人員的素質呢?為此筆者將在本期對敏捷開發中的持續性整合開發進行全面性介紹。

什麽是持續性整合

持續性整合(Continuous Integration)是一種實踐,可以讓團隊在開發過程中持續收到回饋並且進行程式的修正與流程的改進。簡而言之就是在專案的建置過程中,會自動化的反覆不斷進行編譯,依測試的分類運行單元測試、元件功能測試、系統整合測試,產生相關專案資訊、測試結果、程式可靠度報表供分析的開發方式。從測試結果與相關報表的資訊中可以快速的找到錯誤與不妥之處,不必等到開發階段後期才發現系統缺陷。


而因應多人協同開發來說持續性整合必須配合版本控制系統,促使開發團隊中的開發人員頻繁地把他們所新增與修改的程式碼提交至版本控制系統的Repository中,但在提交程式碼之前必須確認加入的程式不會破壞原始碼的編譯與建置。


為了達到快速回饋的目的,持續性整合鼓勵高頻率的Commit(提交程式碼至版本控制庫中)。 持續性整合提供了敏捷開發一個堅實的技術基礎。如果沒有一個完善的持續性整合開發環境,要想高效地進行團隊軟體開發幾乎是不可能的。


接著我們從幾個方面來看持續性整合開發必須要有什麼樣的環境、必須架構那些工具以及必須遵守怎樣的原則。


持續性整合的初始:專案的管理與建置

在以往小型或獨立專案的開發中,專案的建置其實很簡單,開啟一個常用的IDE,執行下拉式選單的[File][New Project…],即可輕鬆的產生針對IDE開發平台的專案設定,而且編譯、封裝與佈署也是直接在IDE開發環境中直接運行。


但對於大型或與多個廠商協同合作所開發的專案就無法這麼做,其原因為Java IDE開發環境為數眾多,除非強制開發人員只能使用特定的開發平台,否則每個開發人員喜好使用的工具都不一定相同。企業級的大型專案也是相同的道理,專案可能同時發包給多個廠商,廠商有錢的用JBuilder開發,經濟拮據的用EclipseNetBeans,如此一來整合就是個難題。


因為如此,在Java持續性整合的開發中有了幾個有名的跨IDE開發平台的專案管理與建置工具。

  • Ant:為Another Neat Tool的縮寫,為Java基礎的建置工具,可用於小型的個人專案,亦可用於大型、多人協同開發的軟體專案。

  • Maven1Apache的專案管理、構建、部署工具,Maven的基本概念就是專案物件模型 (Project Object ModelPOM) 描述專案的各個方面。

  • Maven2Maven的第二代,針對了AntMaven1的不足處進行了全面性的改進,增進建置速度,更少、更簡易的設定檔。


在建置工具的比較中Ant無疑是最靈活也是支援最多的工具,在開放原始碼的社群中還有為數不少的專案是建置在Ant的基礎上,包括知名的Spring FrameworkHibernateAppFuse…等。但也就是因為太過於靈活了,導致同一個功能10個人寫會有10個不同的腳本與設定,小型專案上使用非常方便,但應用在大型或者多專案的建置上經驗的不足將導致災難,將會陷入茫茫的建置與設定檔的大海之中。


Maven1的起源最初是由於進行Jakarta Turbine專案,原先專案中每一個check inCVS的不同的JAR檔都擁有他們自己的build files,在進行維護上相當的困難,因此能夠進行很簡單的建置,並且定義出一個建置多個project的標準、一個對project構成清晰的描述、一個簡單的方式發佈project的資訊與一個能夠通過幾個不同的project去分享不同的JAR檔的方式就成為Maven這個專案的目標。


很幸運的由於Maven1抽象了POM的概念,大部分的專案建置腳本被寫入了Maven1的核心架構Plugin中,如此專案建置過程被Maven1統一了,雖說如此,Maven1還是保留了自行客製化的建置腳本方式,支援Ant並且可依不同的IDE開發環境需求,產生相關的開發平台設定檔。TurbineJakarta CommonsJetspeed2…等都是使用Maven1建置的。


Maven2Maven系列的第二代,但除了POM的設定相似外,核心架構與Plugin的使用卻是大大的不相同,當然也完全不相容,為了因應需求多變的挑戰,它完完全全進行了重寫。Maven2從開放原始碼社群中頡取了十多年專案管理建置的經驗。以更高階的觀點進行抽象,拋棄Maven1的包袱,進行重新設計,當然成果不凡,從Maven1移植到Maven2上後會發現所需撰寫的建置腳本的機會愈來愈少了取而代之的是需要了解相關Plugin的使用,並進行設定,目前有為數不少的社群為Maven2 Plugin的開發無償的奉獻心力。


參考圖 1所示在Maven2中使用mvn archetype:create指定專案範本建立新專案,並可分別執行mvn eclipse:eclipsemvn idea:idea,產生符合EclipseIntelliJ IDEA開發環境的設定。而現在多數的IDE都可以透過更新Plugin的方式提供對Maven2的支援。


1 Maven2IDE平台設定


持續性整合的依據:最重要的事,測試優先

測試驅動開發(Test-Driven DevelopmentTDD)是極端軟體製程的重點特色,也推動了程式碼進行持續性整合。測試驅動開發所強調的就是測試優先,在開發功能程式之前,先撰寫測試程式碼。意謂著開發前必須先思考如何針對這個功能進行測試,並且撰寫完整的測試案例,當開發出來的程式完全滿足測試案例後,再反覆進行這一系列的步驟,直到全部功能的開發完成。


在此簡單的列出Java持續性整合開發中比較常用的測試框架:

  • JUnitJUnit是最知名的單元測試框架,幾乎所有Java測試框架的開發都是繼承於它。

  • JunitPerf:針對了JUnit進行的一個裝飾,用於進行一定時間內的效能與一定壓力下的負載測試。

  • DbUnit:是爲資料庫測試的專案提供的一個對JUnit的支援,用來維護測試前後資料的一致性。

  • HttpUnit:對網路Http協定進行自動完善測試的JUnit擴充框架,一般用於功能與整合測試。

  • Cactus:擴展了 JUnit,進行In-Container(容器內)測試,也就是說必須要啟動Web Server

  • jMock EasyMock:測試用模擬物件框架,某些測試若必須與其他物件相依時,可用MockObject

  • WebTest:進行Web功能性測試的框架,擴展自HttpUnit,用腳本測試,無需寫程式。

  • Selenium:近來滿熱門的Web整合、驗收測試工具,分為Test Runner測試模式(Scenarios進行HTML測試腳本撰寫並測試)以及Driven測試模式(以類似JUnit寫程式碼方式進行功能測試)。前者簡單並且可交由非開發人員進行腳本撰寫但測試流程較無彈性,後者測試靈活彈性高,但需寫如同JUnit一樣的程式碼,須交由開發人員處理。


參考圖 2得知透過JUnit產生報表,可保證在開發的過程中運行測試驅動最重要的功能,也就是增進程式碼的正確與穩定性,能夠在第一時間內發現程式錯誤與效能不佳之處,進行迅速的修正或進行重構,但若想有效的進行測試驅動開發,則必須遵從幾個原則。


首先嚴守測試優先,撰寫某個功能前,先以如何使用作考量,撰寫測試程式碼,再進行功能程式的撰寫,不論反覆多少次的新增功能、修改與重構,都要保持可測試性。筆者發現在以往協同開發的廠商或開發人員都以時程過趕而忽略了撰寫測試程式碼的重要性,等到發現系統過於複雜、藕合過深之後,想進行重構卻也已經來不及。測試優先不單指JUnit單元測試與元件測試,佈署到Web Container後的功能測試也必須嚴守測試優先,以往SeleniumWebTest這類使用腳本進行功能測試的工具還未出現之前,功能測試是非常麻煩的,因此開發人員寧可只單獨進行手工測試,而如今Selenium可以協助我們快速進行測試腳本的建立,但並不代表開發人員可以忽略測試優先的原則,等到系統快開發完成才一口氣寫完所有測試腳本,若是如此將會自掘墳墓,因為就算Selenium工具強大,但少數還是隱函著一些瀏覽器平台相容性的問題。測試優先不是沒有道理的,初期的測試也許會遇到一些小狀況,通常都能順利解決,隨時保持程式的可測試性,臭蟲就不會在開發結案前跳出來咬你一口。


其二必須隔離測試類別,測試分類有單元、功能、整合測試,雖然都可以使用JUnit進行,但本質上卻有不同,參考圖 3可知,每一個不同的測試對應了開發初期不同階段的需求。

  • 單元測試:為對最底層某些類別、方法所撰寫的測試單元,一般而言為測試的最小單位,多數為開發人員為測試最基本的功能是否完備而撰寫的測試邏輯,此處測試只牽涉到基礎單元程式,必須與其他不相關程式模組進行隔離。

  • 功能測試:將基礎建設中的單元進行初次整合所達到的特定功能測試,例如將多個Dao物件整合為ManagerFaçade類別,針對其功能類別進行測試。在功能測試來說可能為必須啟動Web伺服器的容器內測試或無需啟動伺服器的獨立測試,若為獨立測試則必須隔離與Web相關的類別,若不可避免必須相依其他物件的話,可使用MockObject技術配合進行測試。

  • 整合測試:因應系統設計文件所需功能所進行的測試,實作方式較多面,有用JUnit撰寫測試碼方式進行、有用WebTestSelenium撰寫程式或測試腳本方式進行,基本上已進行一定程度的實機測試,測試碼由開發人員撰寫。

  • 驗收測試:因應需求所做的測試,該測試為使用單位進行,因此多為使用者接受度測試,若希望能進行自動化測試的話,可使用Selenium進行腳本側錄,經過簡單的修改就能進行測試,參考圖 4

當考量了必須進行隔離測試的同時,開發人員會很自然的在設計上進行物件的解藕,而達到鬆散藕合的目的,撰寫不同類型測試的同時將專注在同一組功能設計上的考量,自然而然撰寫出來的程式碼將擁有高度的內聚力。


其三撰寫高可讀性的測試程式碼,因為測試碼實質上就是元件使用方式的集合,一般在開發過程中很少有時間針對原始碼撰寫使用手冊或其相關的說明文件,有了可讀性高的測試碼後就像擁有了一份完整的使用手冊,而日後相關文件的撰寫也可直接取用參考。


其四不要用IDE進行偵錯,並養成寫Log的好習慣。不可否認的IDE平台的開發環境,是非常好的開發工具,多數的開發人員也習慣於在IDE中下中斷點進行偵錯,而排斥撰寫單元測試程式碼,認為是多此一舉。但事實上在使用偵錯工具時開發人員不會記得上次的錯誤是什麼,中斷點下在那,並且因為偵錯太過於方便了,多數的開發人員就不會寫Log,就算寫也寫出無效或非關鍵點的Log訊息。若是如此當程式碼已佈署到了Server上,已經是上線的系統時,則根本無從在上線的系統中進行偵錯。


最後撰寫測試碼要小步前進。畢竟軟體開發是個非常複雜的工作,開發過程中必需經過多方面的考量,包括程式的正確性、可擴展性、效能…等,很多衍生的問題也都是程式複雜度太高所引起的。因此建議把大規模,複雜性高的功能,分解成多個小功能來完成,經過一步一步的建置,通過一步一步的測試,就能降低整個系統的複雜性。而低複雜性的程式碼,在日後也將有維護方便,易於重構的優點。


2 JUnit Report


3 測試V型圖


4 Selenium進行網頁功能測試


作者介紹

盧建州 <jazz.lu0827@gmail.com>

有多年軟體開發經驗,注重軟體工程並善用Design Patterns。專研於JavaOpen Source解決方案、跨平台技術與其異質資訊系統整合。目前為自由工作者。



沒有留言: