[測試]自動化測試經驗分享- MS TechDays 2011 BoF內容

  • 33526
  • 0

[測試]自動化測試經驗分享

前言
今年微軟TechDays有幸獲得主辦單位邀請,擔任一場BoF的講師(其實應該算主持人才對),題目是『自動化測試實戰經驗分享』。

初試啼聲的結果,其實對自己的表現相當不滿意。因為原本在這場合的目的,是希望能讓聽眾聽到業界大家在推行自動測試的痛苦失敗經驗,以及如何透過不同的方式,來解決眼前的難題。中間再帶到一些我痛苦到解脫,解脫後又碰到另一種痛苦的過程和突破困境的方式。但碰到時間不足,讓我有點拿捏不好原本準備的劇本。

所以這一篇文章,會將原本想要帶到的一些議題跟想法,補充上來,也算是對大家、對自己有個完整的交代。

課程內容與補充

  1. 為何需要測試
    因為需要信心!測試可以帶來品質的保證,就像在工地高空工作的工人一樣,他們絕不會預期自己會從高空墜落,就如同我們撰寫完程式,不會預期它將出錯。但是高空作業還是需要相對的安全措施,才不會活在恐懼中。沒有測試保護的系統,任何修改系統的動作,都有可能導致其他的Defact發生。這會嚴重的影響到整個團隊,包括使用者、開發人員、分析人員、管理人員的信心,一個不良系統完成後,大家不敢去修改它,而用許多繞圈的方式,來達到目的。結果就是大樓越蓋越歪,因為沒人敢在去動任何一根鋼筋,任何一塊磁磚。

    91:測試可以讓我們,不再只是靠直覺來判斷,系統是否如同預期的運作。讓我們可以不再活在改變的恐懼中。
  2. 無間地獄
    無間地獄,這就是我們絕大部分沒有完整測試的現狀。通常有哪些徵狀?
    1. 系統整合時出現錯誤,是誰的程式導致的錯誤?出錯點不一定是錯誤的根源。
      • 該怎麼證明自己的程式沒有問題?
      • 該怎麼迅速的找出,究竟是哪一個模組出現出乎預料的結果?

        91:測試可以保護自己,可以迅速找出問題,降低尋找問題的成本。
    2. 改了這支程式,其他的程式會不會被影響?
      • 其他程式到底是應該跟著修改,還是不應該被影響?
      • 自己的程式好了就好,管其他人去死?(別忘了『自己』也包含在『其他人』的子集合裡面)

        91:讓迴歸測試結果來回答
    3. 交付的程式,到底測過哪些東西?
      • 為什麼測試報告總是一份Word, Excel文件,那一些checklist的項目,勾選真的有意義嗎?勾選完就可以保證不會出現錯誤嗎?真正的測試報告,應該是即時的,且避免人操作上的主觀跟誤差。
      • 個人觀點:我甚至覺得任何程式交付的動作,應該都要能證明,測過了哪些東西。在這些test case裡面,在test code coverage的範圍裡,可以100%擔保程式如同預期運作。

        91:測試文件應該由程式碼或工具來產生,降低人為誤判,提升速度,即時且數據化的產出系統的健康報告。
    4. 他的功能還沒寫好,我沒法子測我的程式。
      • 程式耦合性太高,在現在的分工模式下,可能會導致開發人員互相等待的情況發生。導致平行開發的速度降低,測試的時間往後延遲,開發人員彼此又用直覺在猜測功能應該如何運作,應該可以正常運作。所以通常在SIT的時候,程式整合完會哀鴻遍野的原因就在這。

        91:可測試性為系統品質的重要指標之一,具備可測試性,可確保程式的耦合性低,可透過Mock模擬外界回應,讓每一個功能單元都更加獨立。
    5. 一盒咖哩塊的故事。
      • 為什麼買其他東西都沒有問題,只有買咖哩塊就會出錯?程式看起來都沒問題,但因為要使用到外部的service,無法在本機端測試。結果:為了測試正式環境上到底出現了什麼問題,隔天就收到了訂購的咖哩塊。

        91:當程式依賴外部服務,才能正常運作,才能測試哪裡出錯,才能還原現場時,要付出的代價可能超乎想像。為了怕這代價,可能只能瞎子摸象,只是變相的成本轉嫁。
    6. 無間地獄的由來:
      佛曰:『受身無間者永遠不死,去壽長乃無間地獄中之大劫。』
      簡單的說,就是『品質低落的系統活越久,維護的人越痛苦。
      斬不斷,理還亂,身陷無窮痛苦迴圈。
  3. 為什麼需要自動測試
    1. 確保bug不會重覆出現。
      Bug的修復成本很高(高在哪?並不是高在功能修正,而是高在測試範圍跟不可預期的連帶影響風險),Bug應該只要修一次。
    2. 確保新程式達到可接受的品質水準。
      每一段新的程式,就像建立起自己的防火巷,即使燒起來,也可以確保不會延燒至其他模組。
    3. 建立與保持開發團隊和使用者的信心。
      看不到,所以心生恐懼。(跟鬼一樣…)當我們可以即時地得到數據化的測試報告時,就可以多一分掌握系統的把握。
    4. 加速回饋循環。
      image
      當系統越晚進行修正,所需付出的成本就越大。什麼時候才會知道哪些東西需要修正,絕大部分都在測試完才會知道。

      所以要降低系統的改變成本,簡單的說,就是把測試的時間點往前推,或是對每個階段都進行測試,來降低未來指數上升的成本負荷。(這也是Agile跟TDD的重要目的之一,程式出現bug以及需求改變,是軟體開發的特色之一,把測試的準備動作,拉到實作,甚至需求、分析跟設計階段,可以大幅降低系統改變成本,提升品質,以及避免系統後期因為時程而犧牲了品質的原罪)
  4. 『程式撰寫』與『測試』哪一個時間花的多?
    直覺上,是程式撰寫花的時間多。內行人會說,測試花的時間多。因為每一行的程式一撰寫完,就進入維護階段了。每一個功能完成就開始進行測試了。(不論是什麼形式的測試)大部分的管理者也都是內行人,但大部分時程預估,卻都是測試時間<開發時間。為什麼會發生這樣的狀況?最常見的原因就是,『先求有,再求好』。這句話很實用,也很無奈。但這句話在軟體開發是有問題的,至少應該修正成『先求對,才會有,再求好』。交付一個有問題的程式,就會變成上面那張曲線圖,先求有的結果,是帶來無止盡、無形的維護成本。

    自動化測試的確需要投入成本,增加的是建置與開發的成本,但對整個系統的成本來說,如果系統需要維護(每個系統都需要維護),是降低的。因為測試跟維護的成本佔了很大的比例。
    自動化測試,就是透過infrastructure,將測試的時間大幅縮短,並提升可信度。以有形的建置與開發成本,降低漫無目的的測試及無形的維護成本。
  5. 自動化測試的六個元素:SEARCH(參考自『軟體測試之道』)
    1. Setup:環境建置。包括版本控管、持續整合(CI)建設、初始化測試環境與測試資料的準備。
    2. Execution:執行。驗證功能、錯誤處理等等…
    3. Analysis:分析是決定測試通過或失敗的程序。也就是要定義什麼樣的情況是成功,什麼樣的情況是失敗。通常包含著domain know-how。
    4. Reporting:報告。指的是分析的呈現和解釋。可能會透過數據報表、圖示、燈號,以不同的方式呈現,例如Portal中的DashBoard、Mail通知等等…
    5. Cleanup:將所有執行完的環境與資料,還原成原本的狀態,以便進行下一輪測試。
    6. Help:其他的輔助,例如如何增加測試案例,如何更有效的整合和回饋到系統的開發與測試。(例如使用FitNesse來讓使用者提供測試案例)
  6. 是不是所有的測試都要自動化
    不一定,這是一種取捨。一味的全面導入自動化測試,通常只會失敗收場。因為初期投入成本過高,穩定性又不足,開發團隊很容易碰到挫折後,失去信心。管理者更會因為成本與效益無法達到目標,而對『自動化測試』失去信心。

    把握80/20法則,從兩個面向當切入點:
    1. 最容易開始自動化測試,不會花什麼成本的方式,開始做自動化測試。例如:效能測試、壓力測試、負載測試、安全測試、單元測試。
    2. 從以往或預估會花最多測試與品質成本的地方,開始導入自動化測試。雖然過程痛苦,但成本回收比會讓前面的辛苦獲得甜美的果實。例如:單元測試、整合測試,準備測試環境、測試資料,初始化與還原的機制。

      單元測試橫跨了上面兩個面向,因為單元測試挑戰的是Developer的能力和開發資源的分配。Developer要寫出具備可測試性的程式,可能是最花成本的一件事(學習成本),但執行單元測試卻是最不花成本,且最穩固、最即時的保護。這也是Developer可以完全操之在己的一種測試。

 

一分鐘到了,你的夢醒了嗎?
歡迎回到現實世界,現實世界畢竟不是烏托邦。即使我們知道了自動化測試的工具、方法,也擁有著開始改變的衝動與熱忱,但我們還是會碰到讓人沮喪氣餒的情況。

  1. 有心殺賊,無力回天
    1. 開發時程沒有將測試時程估進去。
      程式寫得好的話,為什麼測試需要花這麼多時間?
    2. 管理者短視近利。
      就是『先求有,再求好』那一套,縮短測試的時間,將問題歸咎於開發的品質不好。
    3. 團隊抗拒改變。
      按照以前的方式,程式也可以正常運作,為什麼又要搞這麼多有的沒的,又麻煩又花時間又收不到效果。
      不要企圖改變前人的方式與修改版本庫上的東西,他們會存在,有一定的理由。
    4. 沒有測試與建置環境。
      沒有機器,沒有預算,沒有多餘的人管理機器。
    5. 無法提供仿真測試資料。
      客戶無法提供資料。沒有時間做資料。上線後就可以用真實資料來測。
    6. YAGIN,KISS。
      為了還沒發生的問題,花這些時間做這些事,沒有意義。

      91:出來跑,總是要還的…要說服上層拿到時間、人力、硬體資源來進行自動化測試,還要改變其他人的習慣或團隊文化,是幾近於不可能的事情。
  2. 木已成舟
    1. 找不到時間點做測試。
      開發時間都不夠了,還要做什麼測試。
    2. 棕地程式不具備可測試性。
      沒有測試保護就不能重構。沒有重構就無法讓程式具備可測試性。

      91:任何Bug的修復,功能的增加修改,都需要測試來確定自己這次的動作達到需求,這就是最好的時機。面對棕地程式,建議先建立成本低的黑箱整合測試,挑選幾個預計在function coverage或code coverage可以涵蓋到較多範圍的test case著手。有了外層簡單快速的黑箱整合測試保護,再來著手進行棕地程式的重構與修改。在這個同時,將單元測試建立起來,在這一小塊模組中,清除棕地成為綠地。那怕要改的,只有一行code,只要是新的功能,只要是有問題的Defact,為它特地建立一個單元測試,都是值得的。因為Bug的修復成本很高!
  3. 半途而廢,功虧一簣
    1. 維護的人不看測試結果,不寫測試程式。
      我不懂,不想懂,何必懂。
    2. 破窗理論。
      當一個洞不去理他的時候,很快的就會越破越多洞。

      維護人員:曾經有一份完整的測試專案擺在我的面前,但我不懂得珍惜。等到系統壞到不可收拾的時候,才後悔莫及。自動測試最痛苦的事莫過於此。如果上天給我個機會可以重來一次的話,我會對這個測試專案說:我愛她。如果非要在這份愛上面加上一個期限,我希望是直到永遠。

      91:Production code與測試專案,應該是生命共同體,是一體的兩面。每一次的修改,都應有對應的測試案例來證明這一次的修改是符合預期運作的。前面的團隊革命無數次,耗費了多少的心血、努力、時間,才擁有了一份可運作的測試專案,為的就是讓維護人員可以輕鬆的handover,維護人員將測試專案置之不理,就像三國劉備辛苦了大半輩子,最後被一個無能的劉禪把祖業敗掉一樣可悲。

 

變法通常都會失敗
是的,這些痛苦,我們都經歷過,這也是必經的過程。變法通常都會失敗,因為沒人喜歡大掃除,人性抗拒改變。請不用灰心、沮喪,我們還是可以先逐步的建立起自己的烏托邦,用事情結果來改變人,而非用人來改變事情。這通常是導入自動測試的第一步。

文化=多數人的價值觀
改變文化是困難的,因為『文化=多數人的價值觀』,『少數服從多數』是一直存在的隱規則。所以,要改變文化的不二法門,就是讓自己成為多數人。一個很好用的方式,就是以三為一個單位。最小單位就是三人一組,想辦法讓自己成為三人中的二人組,被孤立的那一人,就會跟上,或被淘汰。

任何會增加開發困擾的要求,都很容易造成導入失敗。重點是,如何透過自動測試,來增加他們的生產力,降低他們既存的困擾,最好可以讓他們更輕鬆地開發,有更多的時間可以休息。

可以從哪邊開始

  1. 版本控管 (TFS, SVN, Git, VSS…)
  2. 去除程式的相依性(IoC, Mock)
  3. 建立第一個測試專案與第一個單元測試
  4. 建立一個可自動執行的測試腳本
  5. 爭取主管同意與支持
  6. 找一個可控制風險的目標系統,有意願撰寫自動測試的developer,逐步改善品質,建立文化


環境建置的重點:

  1. 版本控管
  2. 持續整合基礎建設
  3. 獨立且仿真的測試環境


自動化測試常見的錯誤

  1. hard-code的路徑
    測試專案無法獨立運作。
  2. 複雜度太高
    production code寫太爛,導致單元測試撰寫時間過長,異動頻率變高,異動範圍過大,維護測試專案成本提升,信心喪失,放棄單元測試
  3. 難以找出問題
    測出有問題,卻無法迅速知道問題的原因。
  4. 誤報
    誤報分兩種,第一種是對的程式,測試結果報錯。第二種是錯的程式,測試結果報對。後者的嚴重程度比前者大很多,一定要避免。
  5. 龜速測試
    單元測試專案與整合測試專案沒有分開。單元測試應該在任何環境底下(包括沒有資料庫、沒有網路等情況…)可以獨立且迅速運行。整合測試因為會需要外界的service,資料,檔案等等資源的存取,還外加需要初始化與還原環境跟資料,所以相當耗時。

    第一種解決方式,就是降低整合測試的頻率,例如固定一天、一週一次。第二種則是當單元測試結束後,才trigger另一台的整合測試啟動。不管哪一種,單元測試與整合測試專案一定要分開。

    單元測試應具備的特性,FIRST:(參考自代碼整潔之道)
    1. Fast:快速。
    2. Independent:獨立。
    3. Repeatable:可重複。
    4. Self-Validating:可反應驗證結果。單元測試不論成功或失敗,都應該要從測試的reporting直接瞭解其意義或失敗原因。
    5. Timely:及時。單元測試應該恰好在使其通過的production code之前撰寫。


綜效:摻在一起做測試牛丸
既然測試這麼重要,測試才能擁有品質,測試才能證明符合需求,測試完才能知道程式有沒有問題,太晚測試會帶來鉅額的成本,測試報告代表系統的健康報告,測試專案代表系統的保證書。那要怎麼樣做,才會將效益最大化。建議將各類人員負責的事務,都以測試導向來進行整合:

  1. 使用者的需求描述:
    例如透過Fit, FitNesse蒐集最貼近需求的測試案例
  2. 分析人員的功能規格文件
    測試程式的內容,就如同功能描述一般,也是後續測試清單內容的來源。將功能描述與測試程式結合,就能降低分析人員與開發人員之間handover的成本。
  3. 開發人員的SandBox
    透過單元測試,來確保自己寫的code不多不少,確保達成功能需求,確保自己的程式邏輯在Sandbox中如同預期運作。
  4. 測試人員的資訊來源
    有了上面的基礎,測試人員只需要review單元測試報告,並針對非功能性的部分進行自動測試。
  5. 維護人員的保障
    測試專案,可以代表使用者的需求,分析人員的功能,開發人員的邏輯設計,測試人員的品質保證。那維護人員即使原來沒參與系統的開發,對他來說,系統也不會是一個包袱。而是有著穩固的安全網與最貼近事實的資訊來源。


結論
建立第一個測試專案,第一個單元測試,就對了。跨出第一步之後,才能達到目標。

自動化測試,才是『預防勝於治療』的王道。

Reference

  1. 軟體構築美學, Brownfield Application Development in .NET
  2. 軟體測試之道, How We Test Software at Microsoft
  3. 軟體測試實戰 Visual Studio & Team Foundation Server
  4. The Art of Unit Testing With Esamples in .NET
  5. 代碼整潔之道, Clean Code - A Handbook of Agile Software Craftsmanship


補充

單元測試的工具,用Visual Studio Test Framework好,還是用NUnit好?
如果看我之前文章理的測試程式會發現,我之前是都習慣用NUnit。但後來我都改採用Visual Studio裡面內建的單元測試了。原因有幾個:

  1. 快速建立相關測試範本,連命名都會帶過去
  2. 不只可以測試public的function,連private都可以測試。
  3. 不只可以測試library,WebSite與Console Project都可以測試。
  4. 直接搭配Visual Studio的code coverage,在同樣的IDE裡可以知道哪些code有被測試到。

除非用了其他Open source的工具沒有支援Visual Studio內建的測試,不然以我兩個都有用過的經驗,我會推薦使用Visual Studio內建的單元測試工具。

重要的Test Case
什麼樣的Test Case是具備代表性的?

  1. 最常發生的狀況。也就是主要流程或是functional coverage可以涵蓋最多的部分。跟code coverage以程式碼的覆蓋量來區分,functional coverage則是以頻率來區分。
  2. 導致程式碼分歧的資料。也就是透過不同的input,來提升code coverage的比率。
  3. 邊界值。通常邊界值具備一定的商業意義或驗證程式碼本身的error handling完善程度。
  4. 造成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往下那一層開始做整合測試,搭配白箱的單元測試,系統的品質就有很高的保證了。

所以我的建議是:

  1. 把架構切乾淨,讓UI單純化。
  2. 僅錄製主要流程,因為主要流程通常比較穩定。不做太多的Assert驗證,除非該驗證相當重要。避免任何修改,導致主要流程出現crash,無法繼續往下進行。
  3. 錄製假資料的輸入,可以加速人在進行手動測試時,填資料的時間。所以錄製還是很方便的,不一定要全自動才用的到它。
  4. 若真的需要做到UI的自動測試,務必將測試的腳本規劃與切分,將重用的部分拆出來,透過組合呈現不同的Test Suite,就像寫程式一樣,Don't Repeat Yourself,以後異動時,才不用到處修改或漏改。


如何開始第一個測試專案與第一個單元測試
可以參考我之前的文章:
http://www.dotblogs.com.tw/hatelove/category/5036.aspx,前半部為如何整理系統設計與架構,後半部解釋如何開始進行單元測試。


最後最後,我想跟那天來上課的學員們說聲抱歉,那天我自己覺得節奏沒有掌握得很好,一些原本猜想大家會有疑問的部分也沒來得及提出來分享我的經驗,在這篇文章,把我原本準備的內容以文字的方式呈現出來,希望可以幫助到更多的人,也希望可以補足學員們上課中聽到的內容。

另外,感謝黑暗執行緒大哥幫忙做的筆記,看到他的筆記,讓我有點感動,因為我覺得我講的不是挺清楚的,他卻將我真的想講的點都記錄的相當明確。在這邊也附上連結供大家參考與回憶一下當天上課的內容:from 黑暗執行緒:TechDays 2011隨堂筆記0914

 


blog 與課程更新內容,請前往新站位置:http://tdd.best/