[測試]自動化測試經驗分享
前言
今年微軟TechDays有幸獲得主辦單位邀請,擔任一場BoF的講師(其實應該算主持人才對),題目是『自動化測試實戰經驗分享』。
初試啼聲的結果,其實對自己的表現相當不滿意。因為原本在這場合的目的,是希望能讓聽眾聽到業界大家在推行自動測試的痛苦失敗經驗,以及如何透過不同的方式,來解決眼前的難題。中間再帶到一些我痛苦到解脫,解脫後又碰到另一種痛苦的過程和突破困境的方式。但碰到時間不足,讓我有點拿捏不好原本準備的劇本。
所以這一篇文章,會將原本想要帶到的一些議題跟想法,補充上來,也算是對大家、對自己有個完整的交代。
課程內容與補充
-
為何需要測試
因為需要信心!測試可以帶來品質的保證,就像在工地高空工作的工人一樣,他們絕不會預期自己會從高空墜落,就如同我們撰寫完程式,不會預期它將出錯。但是高空作業還是需要相對的安全措施,才不會活在恐懼中。沒有測試保護的系統,任何修改系統的動作,都有可能導致其他的Defact發生。這會嚴重的影響到整個團隊,包括使用者、開發人員、分析人員、管理人員的信心,一個不良系統完成後,大家不敢去修改它,而用許多繞圈的方式,來達到目的。結果就是大樓越蓋越歪,因為沒人敢在去動任何一根鋼筋,任何一塊磁磚。
91:測試可以讓我們,不再只是靠直覺來判斷,系統是否如同預期的運作。讓我們可以不再活在改變的恐懼中。 -
無間地獄
無間地獄,這就是我們絕大部分沒有完整測試的現狀。通常有哪些徵狀?-
系統整合時出現錯誤,是誰的程式導致的錯誤?出錯點不一定是錯誤的根源。
- 該怎麼證明自己的程式沒有問題?
-
該怎麼迅速的找出,究竟是哪一個模組出現出乎預料的結果?
91:測試可以保護自己,可以迅速找出問題,降低尋找問題的成本。
-
改了這支程式,其他的程式會不會被影響?
- 其他程式到底是應該跟著修改,還是不應該被影響?
-
自己的程式好了就好,管其他人去死?(別忘了『自己』也包含在『其他人』的子集合裡面)
91:讓迴歸測試結果來回答
-
交付的程式,到底測過哪些東西?
- 為什麼測試報告總是一份Word, Excel文件,那一些checklist的項目,勾選真的有意義嗎?勾選完就可以保證不會出現錯誤嗎?真正的測試報告,應該是即時的,且避免人操作上的主觀跟誤差。
-
個人觀點:我甚至覺得任何程式交付的動作,應該都要能證明,測過了哪些東西。在這些test case裡面,在test code coverage的範圍裡,可以100%擔保程式如同預期運作。
91:測試文件應該由程式碼或工具來產生,降低人為誤判,提升速度,即時且數據化的產出系統的健康報告。
-
他的功能還沒寫好,我沒法子測我的程式。
-
程式耦合性太高,在現在的分工模式下,可能會導致開發人員互相等待的情況發生。導致平行開發的速度降低,測試的時間往後延遲,開發人員彼此又用直覺在猜測功能應該如何運作,應該可以正常運作。所以通常在SIT的時候,程式整合完會哀鴻遍野的原因就在這。
91:可測試性為系統品質的重要指標之一,具備可測試性,可確保程式的耦合性低,可透過Mock模擬外界回應,讓每一個功能單元都更加獨立。
-
程式耦合性太高,在現在的分工模式下,可能會導致開發人員互相等待的情況發生。導致平行開發的速度降低,測試的時間往後延遲,開發人員彼此又用直覺在猜測功能應該如何運作,應該可以正常運作。所以通常在SIT的時候,程式整合完會哀鴻遍野的原因就在這。
-
一盒咖哩塊的故事。
-
為什麼買其他東西都沒有問題,只有買咖哩塊就會出錯?程式看起來都沒問題,但因為要使用到外部的service,無法在本機端測試。結果:為了測試正式環境上到底出現了什麼問題,隔天就收到了訂購的咖哩塊。
91:當程式依賴外部服務,才能正常運作,才能測試哪裡出錯,才能還原現場時,要付出的代價可能超乎想像。為了怕這代價,可能只能瞎子摸象,只是變相的成本轉嫁。
-
為什麼買其他東西都沒有問題,只有買咖哩塊就會出錯?程式看起來都沒問題,但因為要使用到外部的service,無法在本機端測試。結果:為了測試正式環境上到底出現了什麼問題,隔天就收到了訂購的咖哩塊。
-
無間地獄的由來:
佛曰:『受身無間者永遠不死,去壽長乃無間地獄中之大劫。』
簡單的說,就是『品質低落的系統活越久,維護的人越痛苦。』
斬不斷,理還亂,身陷無窮痛苦迴圈。
-
系統整合時出現錯誤,是誰的程式導致的錯誤?出錯點不一定是錯誤的根源。
-
為什麼需要自動測試
-
確保bug不會重覆出現。
Bug的修復成本很高(高在哪?並不是高在功能修正,而是高在測試範圍跟不可預期的連帶影響風險),Bug應該只要修一次。 -
確保新程式達到可接受的品質水準。
每一段新的程式,就像建立起自己的防火巷,即使燒起來,也可以確保不會延燒至其他模組。 -
建立與保持開發團隊和使用者的信心。
看不到,所以心生恐懼。(跟鬼一樣…)當我們可以即時地得到數據化的測試報告時,就可以多一分掌握系統的把握。 -
加速回饋循環。
當系統越晚進行修正,所需付出的成本就越大。什麼時候才會知道哪些東西需要修正,絕大部分都在測試完才會知道。
所以要降低系統的改變成本,簡單的說,就是把測試的時間點往前推,或是對每個階段都進行測試,來降低未來指數上升的成本負荷。(這也是Agile跟TDD的重要目的之一,程式出現bug以及需求改變,是軟體開發的特色之一,把測試的準備動作,拉到實作,甚至需求、分析跟設計階段,可以大幅降低系統改變成本,提升品質,以及避免系統後期因為時程而犧牲了品質的原罪)
-
確保bug不會重覆出現。
-
『程式撰寫』與『測試』哪一個時間花的多?
直覺上,是程式撰寫花的時間多。內行人會說,測試花的時間多。因為每一行的程式一撰寫完,就進入維護階段了。每一個功能完成就開始進行測試了。(不論是什麼形式的測試)大部分的管理者也都是內行人,但大部分時程預估,卻都是測試時間<開發時間。為什麼會發生這樣的狀況?最常見的原因就是,『先求有,再求好』。這句話很實用,也很無奈。但這句話在軟體開發是有問題的,至少應該修正成『先求對,才會有,再求好』。交付一個有問題的程式,就會變成上面那張曲線圖,先求有的結果,是帶來無止盡、無形的維護成本。
自動化測試的確需要投入成本,增加的是建置與開發的成本,但對整個系統的成本來說,如果系統需要維護(每個系統都需要維護),是降低的。因為測試跟維護的成本佔了很大的比例。
自動化測試,就是透過infrastructure,將測試的時間大幅縮短,並提升可信度。以有形的建置與開發成本,降低漫無目的的測試及無形的維護成本。 -
自動化測試的六個元素:SEARCH(參考自『軟體測試之道』)
- Setup:環境建置。包括版本控管、持續整合(CI)建設、初始化測試環境與測試資料的準備。
- Execution:執行。驗證功能、錯誤處理等等…
- Analysis:分析是決定測試通過或失敗的程序。也就是要定義什麼樣的情況是成功,什麼樣的情況是失敗。通常包含著domain know-how。
- Reporting:報告。指的是分析的呈現和解釋。可能會透過數據報表、圖示、燈號,以不同的方式呈現,例如Portal中的DashBoard、Mail通知等等…
- Cleanup:將所有執行完的環境與資料,還原成原本的狀態,以便進行下一輪測試。
- Help:其他的輔助,例如如何增加測試案例,如何更有效的整合和回饋到系統的開發與測試。(例如使用FitNesse來讓使用者提供測試案例)
-
是不是所有的測試都要自動化
不一定,這是一種取捨。一味的全面導入自動化測試,通常只會失敗收場。因為初期投入成本過高,穩定性又不足,開發團隊很容易碰到挫折後,失去信心。管理者更會因為成本與效益無法達到目標,而對『自動化測試』失去信心。
把握80/20法則,從兩個面向當切入點:- 最容易開始自動化測試,不會花什麼成本的方式,開始做自動化測試。例如:效能測試、壓力測試、負載測試、安全測試、單元測試。
-
從以往或預估會花最多測試與品質成本的地方,開始導入自動化測試。雖然過程痛苦,但成本回收比會讓前面的辛苦獲得甜美的果實。例如:單元測試、整合測試,準備測試環境、測試資料,初始化與還原的機制。
單元測試橫跨了上面兩個面向,因為單元測試挑戰的是Developer的能力和開發資源的分配。Developer要寫出具備可測試性的程式,可能是最花成本的一件事(學習成本),但執行單元測試卻是最不花成本,且最穩固、最即時的保護。這也是Developer可以完全操之在己的一種測試。
一分鐘到了,你的夢醒了嗎?
歡迎回到現實世界,現實世界畢竟不是烏托邦。即使我們知道了自動化測試的工具、方法,也擁有著開始改變的衝動與熱忱,但我們還是會碰到讓人沮喪氣餒的情況。
-
有心殺賊,無力回天
-
開發時程沒有將測試時程估進去。
程式寫得好的話,為什麼測試需要花這麼多時間? -
管理者短視近利。
就是『先求有,再求好』那一套,縮短測試的時間,將問題歸咎於開發的品質不好。 -
團隊抗拒改變。
按照以前的方式,程式也可以正常運作,為什麼又要搞這麼多有的沒的,又麻煩又花時間又收不到效果。
不要企圖改變前人的方式與修改版本庫上的東西,他們會存在,有一定的理由。 -
沒有測試與建置環境。
沒有機器,沒有預算,沒有多餘的人管理機器。 -
無法提供仿真測試資料。
客戶無法提供資料。沒有時間做資料。上線後就可以用真實資料來測。 -
YAGIN,KISS。
為了還沒發生的問題,花這些時間做這些事,沒有意義。
91:出來跑,總是要還的…要說服上層拿到時間、人力、硬體資源來進行自動化測試,還要改變其他人的習慣或團隊文化,是幾近於不可能的事情。
-
開發時程沒有將測試時程估進去。
-
木已成舟
-
找不到時間點做測試。
開發時間都不夠了,還要做什麼測試。 -
棕地程式不具備可測試性。
沒有測試保護就不能重構。沒有重構就無法讓程式具備可測試性。
91:任何Bug的修復,功能的增加修改,都需要測試來確定自己這次的動作達到需求,這就是最好的時機。面對棕地程式,建議先建立成本低的黑箱整合測試,挑選幾個預計在function coverage或code coverage可以涵蓋到較多範圍的test case著手。有了外層簡單快速的黑箱整合測試保護,再來著手進行棕地程式的重構與修改。在這個同時,將單元測試建立起來,在這一小塊模組中,清除棕地成為綠地。那怕要改的,只有一行code,只要是新的功能,只要是有問題的Defact,為它特地建立一個單元測試,都是值得的。因為Bug的修復成本很高!
-
找不到時間點做測試。
-
半途而廢,功虧一簣
-
維護的人不看測試結果,不寫測試程式。
我不懂,不想懂,何必懂。 -
破窗理論。
當一個洞不去理他的時候,很快的就會越破越多洞。
維護人員:曾經有一份完整的測試專案擺在我的面前,但我不懂得珍惜。等到系統壞到不可收拾的時候,才後悔莫及。自動測試最痛苦的事莫過於此。如果上天給我個機會可以重來一次的話,我會對這個測試專案說:我愛她。如果非要在這份愛上面加上一個期限,我希望是直到永遠。
91:Production code與測試專案,應該是生命共同體,是一體的兩面。每一次的修改,都應有對應的測試案例來證明這一次的修改是符合預期運作的。前面的團隊革命無數次,耗費了多少的心血、努力、時間,才擁有了一份可運作的測試專案,為的就是讓維護人員可以輕鬆的handover,維護人員將測試專案置之不理,就像三國劉備辛苦了大半輩子,最後被一個無能的劉禪把祖業敗掉一樣可悲。
-
維護的人不看測試結果,不寫測試程式。
變法通常都會失敗
是的,這些痛苦,我們都經歷過,這也是必經的過程。變法通常都會失敗,因為沒人喜歡大掃除,人性抗拒改變。請不用灰心、沮喪,我們還是可以先逐步的建立起自己的烏托邦,用事情結果來改變人,而非用人來改變事情。這通常是導入自動測試的第一步。
文化=多數人的價值觀
改變文化是困難的,因為『文化=多數人的價值觀』,『少數服從多數』是一直存在的隱規則。所以,要改變文化的不二法門,就是讓自己成為多數人。一個很好用的方式,就是以三為一個單位。最小單位就是三人一組,想辦法讓自己成為三人中的二人組,被孤立的那一人,就會跟上,或被淘汰。
任何會增加開發困擾的要求,都很容易造成導入失敗。重點是,如何透過自動測試,來增加他們的生產力,降低他們既存的困擾,最好可以讓他們更輕鬆地開發,有更多的時間可以休息。
可以從哪邊開始
- 版本控管 (TFS, SVN, Git, VSS…)
- 去除程式的相依性(IoC, Mock)
- 建立第一個測試專案與第一個單元測試
- 建立一個可自動執行的測試腳本
- 爭取主管同意與支持
- 找一個可控制風險的目標系統,有意願撰寫自動測試的developer,逐步改善品質,建立文化
環境建置的重點:
- 版本控管
- 持續整合基礎建設
- 獨立且仿真的測試環境
自動化測試常見的錯誤
-
hard-code的路徑
測試專案無法獨立運作。 -
複雜度太高
production code寫太爛,導致單元測試撰寫時間過長,異動頻率變高,異動範圍過大,維護測試專案成本提升,信心喪失,放棄單元測試 -
難以找出問題
測出有問題,卻無法迅速知道問題的原因。 -
誤報
誤報分兩種,第一種是對的程式,測試結果報錯。第二種是錯的程式,測試結果報對。後者的嚴重程度比前者大很多,一定要避免。 -
龜速測試
單元測試專案與整合測試專案沒有分開。單元測試應該在任何環境底下(包括沒有資料庫、沒有網路等情況…)可以獨立且迅速運行。整合測試因為會需要外界的service,資料,檔案等等資源的存取,還外加需要初始化與還原環境跟資料,所以相當耗時。
第一種解決方式,就是降低整合測試的頻率,例如固定一天、一週一次。第二種則是當單元測試結束後,才trigger另一台的整合測試啟動。不管哪一種,單元測試與整合測試專案一定要分開。
單元測試應具備的特性,FIRST:(參考自代碼整潔之道)- Fast:快速。
- Independent:獨立。
- Repeatable:可重複。
- Self-Validating:可反應驗證結果。單元測試不論成功或失敗,都應該要從測試的reporting直接瞭解其意義或失敗原因。
- Timely:及時。單元測試應該恰好在使其通過的production code之前撰寫。
綜效:摻在一起做測試牛丸
既然測試這麼重要,測試才能擁有品質,測試才能證明符合需求,測試完才能知道程式有沒有問題,太晚測試會帶來鉅額的成本,測試報告代表系統的健康報告,測試專案代表系統的保證書。那要怎麼樣做,才會將效益最大化。建議將各類人員負責的事務,都以測試導向來進行整合:
-
使用者的需求描述:
例如透過Fit, FitNesse蒐集最貼近需求的測試案例 -
分析人員的功能規格文件
測試程式的內容,就如同功能描述一般,也是後續測試清單內容的來源。將功能描述與測試程式結合,就能降低分析人員與開發人員之間handover的成本。 -
開發人員的SandBox
透過單元測試,來確保自己寫的code不多不少,確保達成功能需求,確保自己的程式邏輯在Sandbox中如同預期運作。 -
測試人員的資訊來源
有了上面的基礎,測試人員只需要review單元測試報告,並針對非功能性的部分進行自動測試。 -
維護人員的保障
測試專案,可以代表使用者的需求,分析人員的功能,開發人員的邏輯設計,測試人員的品質保證。那維護人員即使原來沒參與系統的開發,對他來說,系統也不會是一個包袱。而是有著穩固的安全網與最貼近事實的資訊來源。
結論
建立第一個測試專案,第一個單元測試,就對了。跨出第一步之後,才能達到目標。
自動化測試,才是『預防勝於治療』的王道。
Reference
- 軟體構築美學, Brownfield Application Development in .NET
- 軟體測試之道, How We Test Software at Microsoft
- 軟體測試實戰 Visual Studio & Team Foundation Server
- The Art of Unit Testing With Esamples in .NET
- 代碼整潔之道, Clean Code - A Handbook of Agile Software Craftsmanship
補充
單元測試的工具,用Visual Studio Test Framework好,還是用NUnit好?
如果看我之前文章理的測試程式會發現,我之前是都習慣用NUnit。但後來我都改採用Visual Studio裡面內建的單元測試了。原因有幾個:
- 快速建立相關測試範本,連命名都會帶過去
- 不只可以測試public的function,連private都可以測試。
- 不只可以測試library,WebSite與Console Project都可以測試。
- 直接搭配Visual Studio的code coverage,在同樣的IDE裡可以知道哪些code有被測試到。
除非用了其他Open source的工具沒有支援Visual Studio內建的測試,不然以我兩個都有用過的經驗,我會推薦使用Visual Studio內建的單元測試工具。
重要的Test Case
什麼樣的Test Case是具備代表性的?
- 最常發生的狀況。也就是主要流程或是functional coverage可以涵蓋最多的部分。跟code coverage以程式碼的覆蓋量來區分,functional coverage則是以頻率來區分。
- 導致程式碼分歧的資料。也就是透過不同的input,來提升code coverage的比率。
- 邊界值。通常邊界值具備一定的商業意義或驗證程式碼本身的error handling完善程度。
- 造成bug的資料。每一個bug,都具備著代表性。因為超出原本預期的狀況,這次會發生,下次也可能會發生。這裡會發生,其他地方也可能會發生。所以,珍惜每一次的bug fix吧,把該bug的input加入我們的單元測試,我們的系統品質將多一份保障。
Code coverage的迷思
Code coverage要100%才算測試完整嗎?是,以完整來說,要100%才代表每一行程式碼都有被測試過。但實務上,我還是認為採80/20法則比較有效益。Code coverage可以當作一個健康指標,當作一個檢核的數據,開發人員應該要能解釋這樣的數據,在根據實際狀況來決定是否調整與增加資源。
與其強調code coverage,我個人認為更重要的是實踐單元測試,有2支程式,一支code coverage 100%,另一支0%。還不如這2支程式各50%來得完善。
如果沒有太多資源來增加code coverage,那建議要以functional coverage角度思考。舉例來說,一個function裡面有10行程式碼。其中1行是我們認為一定會跑的(預估functional coverage為100%),其他9行則是防呆(正常狀況下不可能會跑),那我會認為,測到一定會跑得這一行,要比測其他9行有意義的多。
只要有了開發的SOP,基礎建設,設計出來的程式是具備可測試性,那麼隨時可以增加test case,每一次的修正,都可以證明下一次不會發生同樣的錯誤,這樣就很足夠了。
流程注意事項
有一個很重要的事情,在上面文章中沒有提到,就是當自動測試出現紅燈,測試失敗時,這時團隊所有成員應該將此問題的priority列到最高,當問題發生了置之不理,只會讓後面大樓越來越歪,讓前面前功盡棄,讓後面的成本更高。
不過這不代表所有人都要pending到測試成功,而是指出測試失敗的重要性與優先權。個人經驗,只要到找出負責的人去進行修復,其他人就可以留心這個測試失敗的原因,並繼續進行工作。
UI到底要不要做自動化測試
這是一個有趣的issue。UI到底要不要做自動化測試?其實UI的自動化測試是一件測試成本頗低,維護成本極高的測試。現在很多測試工具都是直接提供『錄製』並自動產生UI測試程式碼的功能(例如:VS2010, Selenium, Watir/WatiN),甚至可以給非技術人員來進行錄製的動作。但UI是最容易改變的部分,每次改變我們都需要去維護我們的UI自動測試專案。還有,通常這樣的測試只能測試『值』如同預期,流程是否卡住等等…是無法檢視破圖之類的問題,也就是UI還是有部分勢必還是需要人來進行測試。
另外一個角度,當架構切的夠乾淨,例如MVVM, MVC等等的方式,UI層可以完全無關商業邏輯時,那我的建議是從UI往下那一層開始做整合測試,搭配白箱的單元測試,系統的品質就有很高的保證了。
所以我的建議是:
- 把架構切乾淨,讓UI單純化。
- 僅錄製主要流程,因為主要流程通常比較穩定。不做太多的Assert驗證,除非該驗證相當重要。避免任何修改,導致主要流程出現crash,無法繼續往下進行。
- 錄製假資料的輸入,可以加速人在進行手動測試時,填資料的時間。所以錄製還是很方便的,不一定要全自動才用的到它。
- 若真的需要做到UI的自動測試,務必將測試的腳本規劃與切分,將重用的部分拆出來,透過組合呈現不同的Test Suite,就像寫程式一樣,Don't Repeat Yourself,以後異動時,才不用到處修改或漏改。
如何開始第一個測試專案與第一個單元測試
可以參考我之前的文章:http://www.dotblogs.com.tw/hatelove/category/5036.aspx,前半部為如何整理系統設計與架構,後半部解釋如何開始進行單元測試。
最後最後,我想跟那天來上課的學員們說聲抱歉,那天我自己覺得節奏沒有掌握得很好,一些原本猜想大家會有疑問的部分也沒來得及提出來分享我的經驗,在這篇文章,把我原本準備的內容以文字的方式呈現出來,希望可以幫助到更多的人,也希望可以補足學員們上課中聽到的內容。
另外,感謝黑暗執行緒大哥幫忙做的筆記,看到他的筆記,讓我有點感動,因為我覺得我講的不是挺清楚的,他卻將我真的想講的點都記錄的相當明確。在這邊也附上連結供大家參考與回憶一下當天上課的內容:from 黑暗執行緒:TechDays 2011隨堂筆記0914
blog 與課程更新內容,請前往新站位置:http://tdd.best/