軟體開發有好多個面向,分析設計開發測試維護等等。
測試是其中一環,傻蛋以前是覺得,這是重要性被人低估的一環。
只有規模較大的產品團隊才會有專門的測試工程師,一般小公司常常都是開發人員自己測,主管或需求單位驗收測試,簡單草率了事。
就算同樣是工程師,軟體測試工程師,仿佛在業界,薪資也會比軟體開發的工程師低一些。
測試還是很重要,也值得分工的。
而測試的領域也還有分多種測試,單元測試,整合測試,壓力測試,與最後的驗收測試。
測試也不是隨便點一點,沒看到報錯就算測完了,比較正經的測試,同樣是需要做分析設計的,要做好的測試計畫,以達到期望的品質,這個部分需要的專業能力,其實和開發差不多。
總之,測試是個重要,但通常被低估的領域吧。
而在十來年前,流行過一種叫測試驅動設計的一種軟體開發方法。
Test Driven Design,簡稱TDD。
傻蛋以前了解到這名詞的時候,知道這個定義,是覺得這很好。
如果我是開發工程師,然後團隊可以被允許導入TDD的開發模式,那好像就,太爽了。
這個TDD,簡單說就是先把系統的結果都想好,然後寫出測試,再根據結果反推前面的設計那樣。
那真的是,過太爽啦。
軟體開發最大的困難往往不是技術,甚至也不是內部政治問題,而是需求本身不明確。
大家的想法都不一樣,各有各的立場與利益,對於軟體應該怎麼運作,通常只有個朦朧的大概的共識而已。
除非是一人專案,或絕對的獨裁式的一個核心開發者那樣的運作,一般來說,軟體開發最花時間的,就是確認需求規格這一段流程。
有的團隊可能會用所謂的敏捷式開發,邊做邊確認邊改,那樣會感覺好像大部分的時間都在實際的做開發,而不是做需求分析規格確認這樣的感覺上沒實際產出的工作。
實際上,敏捷的不斷迭帶,就是不斷返工,這都是為了把需求給搞清楚所付出的代價而已,只是形式不同罷了。
想想喔,測試驅動設計,能不能敏捷呢?
很顯然,幾乎是不能的,因為一開始根本沒辦法想好最終結果該是怎樣。
如果是個需求很明確,變動性不高,只需要技術攻關的軟體系統開發,測試驅動設計,可能是很棒的。
因為那會強迫需求方與系統分析師一開始就做周延完善的思考,否則不太可能寫得出完整可行的測試案例。
如果身為基層的開發工程師,有明確定義的規格,甚至連寫好程式後驗證的測試案例資料都準備好了,說真的,那還真是爽得不要不要的唷。
傻蛋是覺得,如果需求規格有辦法完整到連測試案例都完善了,其實開發的作業甚至有機會自動化,讓程式自己根據輸入的需求產生原本需要人開發程式出來。
再總之呢,測試驅動設計是好東西,但是對大部分的軟體工程師來說,應該是用不太到的吧,傻蛋是這麼覺得的。
所以?
軟體測試確實很重要,但通常不是那麼受重視。
特別是需求還不穩定,上面的想法都還有猶疑的時候,別說甚麼測試驅動設計了,一些內部用的系統,就連系統整合測試與驗收測試的測試計畫可能都沒做。
原因很簡單,沒人力沒資源,還有,更完善的軟體品質,不值得花費超過開發成本的代價去做測試。
有錯誤,馬上修正就好,整個需求都是失敗的設想,那就整個丟掉重來也可以。
讓開發的速度更快,只追求一定程度的品質就好,以靈活應變,對那些和生命財產沒有緊密關聯的一般商業應用來說,多半就是這樣而已。
所以呢,傻蛋過去的經驗,是自己測試。
重要的情境要完整跑過一次,確保一切都如自己預期的進行。
通常,這樣測一輪,多少都會發現問題,需要加以修正的。
然後這樣順過一輪後,基本上就可以大膽的上線了那樣,因為速度通常很重要。
而之後的需求變更,或是發現錯誤後的修正,也多半是簡單的測試。
測試針對的新功能或修復的功能是否確認沒問題。
同時根據經驗,對這次的異動有可能影響到的其他相關功能也再測試一下,來確認沒有產生副作用。
嗯,只有這樣。
甚至,在緊急又忙碌的時候,連後面的可能相關影響再測試都沒空做。
有時候就會有沒想到的臭蟲被引發出來。
然後?
馬上修復就好了,頂多是修復時多小心點避免再讓臭蟲連鎖產生就好。
客戶,老闆或主管會因此而處罰工程師嗎?
通常,只要不是太誇張的粗心,是不會處罰工程師的。
因為如果他們決定要對這種不夠細心做處罰,那麼下面的工程師就會變得非常的細心,然後動作慢到他們無法忍受的程度那樣。
而且動作慢只是避免那些容易怪罪到工程師頭上的責任的臭蟲而已,那些商業邏輯本身有缺陷產生的問題是避不了的,只減少一些不太重要的臭蟲發生率,而讓整個團隊的速度大幅降低,那是不划算的。
所以,真的,沒有專業測試編制的組織內,軟體品質是良心事業,是開發工程師自己要顧,而且是要衡量生產力和品質後做取捨折衷,而不是甚麼追求品質完美的極致那樣的。
所以,如果負責開發的工程師團隊沒有稍微有經驗,知道要如何測試的工程師在,急就章的軟體開發,那個品質通常是,慘不忍睹的。
所以通常比較重要的產品,都會配置測試團隊,並制定測試流程計畫,經過重重把關之後才可以上線更新的。
開發成本會膨脹好幾倍,這,都是必要的代價吧。
以上內容和本文主題的關聯不是很大。
本文主題是要談軟體的單元測試這回事。
寫好一小段程式,就針對這一段程式做測試,確認這段程式是沒問題的,這個動作可以稱作為單元測試。
單元測試的實現方式,可以是人去執行程式,人去判斷程式的運作正確,完全人工。
也可以像上面那種完整測試計畫,設計測試流程與測試案例的數據,可以用程式加上數據來做測試。
後面這樣的測試,可想而知,會很費工。
但,這樣的測試,有好處,就是可以反覆的測試,只需要一瞬間就能確認測試是否通過。
如果系統很複雜,改這個會影響那個的情況很普遍,那可以程式化的自動化測試就很重要,相關流程程式全部重跑一次之前的測試案例,有沒通過的,就是需要檢查確認的點,這樣是很有幫助的。
而當單元測試就已經有錯了,就先針對錯誤修復,這樣做系統整合測試時發生的錯誤,通常會比較好解決,因為那可以大幅減少需要懷疑的點,也讓問題容易自動曝露出來那樣。
所以,單元測試應該是重要的好東西吧?
嗯嗯,事情,其實沒有那麼簡單。
傻蛋最近看網路上的CS61B課程,上面有教Java與資料結構和演算法。
其中傻蛋對於測試,特別是現在軟體開發潮流幾乎是必備的單元測試框架工具,很好奇。
結果看到其中一個連結,裏頭的標題是測試驅動設計已死,單元測試是浪費時間。
喔喔!
然後再從連結看到一篇說單元測試是垃圾的英文文章,花了不小力氣讀完。
單字多了點,查單字讀不快。
不過讀完倒是頗有收穫。
單元測試很困難,還有另一個面向需要考量。
如果是上個世紀的程式語言,都是那種一行一行執行到底的命令列程式語言環境,程序導向的軟體開發,那個時候,單元測試很重要,也很有價值。
因為相同的輸入必定得到相同的結果,一個系統又可以拆解成一個個的小流程,每個流程都可以是單獨的函數或子程序那樣,只要相同的環境與輸入,就必須得到相同的預期結果。
這樣寫測試案例,單元測試的單位就是切好的函數或子程序為基本單位就是了,然後測試如果通過,通常不會有太多意外。
然而現在的軟體程式開發,主流,是物件導向的程式語言。
系統運作的流程,和以前的程序導向程式語言,不一樣。
系統是許多物件互動運作出來的,並沒有固定的流程情境,而物件與物件的互動,可以有更多執行時期的組合,想要窮舉所有可能的情境,然後編寫完善的測試案例,就會有些,不切實際。
說簡單點,就是每個設計出來的物件的屬性方法都很好懂,看起來每個物件的方法也都沒錯,但整個運作起來卻可能不是預期想要的行為。
這很不好理解。
這是正常的。
軟體系統是為了實現某些實務上的需求,然而需求本身就不是屬性與方法組成的物件,需求會有情境與流程,然後物件就只是物件,只是一堆抽象的物件屬性與方法,意圖與流程會存在於眾多分析設計出來的物件彼此運作的情境之下。
可能要真的寫過物件導向系統的人才比較容易理解吧。
有些業務知識,可能是不存在某一個物件內的,而是某個物件和另一個物件在進行某個互動時需要遵守特定的限制規範。
喔,所以可以設計高一層的物件把另外兩個物件包進去,這樣就可以把那個限制規範納入在高一層物件內的方法裡了吧?
是這樣沒錯,但是這會讓設計變得僵化,為了一條規則就多一個新物件類別,然後碰到某些共用又需要不同規則的情況,設計就會更形複雜。
物件導向程式開發出來的系統,如果沒有特別良好的設計,通常不好懂。
很難抓運作流程機制是藏在哪裡實現的。
這麼複雜的東西怎麼測試呢?
傻蛋的經驗是,物件導向的系統,測試物件的方法意義很有限。
針對物件程式碼做單元測試,感覺不出有任何意義。
那要怎麼測試?
其實只能根據業務流程去做測試,也就是只做整合測試,如果想做可以自動化的測試,就要衡量成本效益,因為自動化測試的設計,那個工會是把功能開發出來的好幾倍,那都是正常的。
需求還不穩定,隨時會因為反饋而修改的,就別設計自動化測試了,人工測試一下就好,才符合成本效益。
所以呢?
傻蛋還真沒有仔細想過,物件導向的系統設計裡,貌似不知道怎麼做所謂的單元測試喔!
而傻蛋讀到的那個英文長文,測試是為了提供有價值的反饋資訊,物件導向設計之下的單元測試,那個單元到底是什麼定義?
每個物件每一個方法都是需要測試的單元那樣嗎?
除非把所有系統流程強制性的全都設計進物件裡的方法,把程序導向設計的架構硬塞進物件導向的皮裡面,否則,即使每個各別的方法都正確,物件被調用時的情境千變萬化,那部分根本測不到啊。
那就會回到,單元測是和整合測是直接混在一起的情況了。
所以,對,物件導向的物件,傻蛋好像都沒做過自動化的單元測試,連想都沒想過。
那現在流行的軟體開發框架的單元測試又是啥?
這玩意兒傻蛋之前不熟。
一開始的印象,就是某種測試軟體可以幫忙自動測試。
那是錯的。
後來稍微接觸了些自動整合與自動佈署的作業,才知道那是什麼。
基本上,那也是對所有的涵式與子程序寫測試,提供的機制就是宣稱,宣稱在執行到這個地方的時候某個變數值應該符合指定的檢查規則,大概就是這樣。
然後在代碼到處插入這些宣稱,這樣的話,當測試案例一失敗就表示有不符合預期的行為需要處理。
這衍生了新的機制,並引入了新的管理指標,叫測試代碼覆蓋率。
每一行程式碼都有對應的宣稱,驗證其有效,可以叫做代碼覆蓋率百分之百。
追求百分之八十以上的代碼覆蓋率,可以的話最好是百分之百。
覆蓋率越高,品質應該越好,對吧?
傻蛋大概理解這些測是框架之後,嗯,是否該多做點努力來提升系統的品質呢?
而且自動化整合佈署的機制,會把自動化測試也放進來,可以看到程式佈署的過程,自動重新執行所有之前的測試案例,全部綠色勾勾都確認,才會繼續完成程式的更新動作,好像很酷吧?
然後呢?
傻蛋還是沒有嘗試去增加這種單元測試框架的使用。
為什麼?
主要是浪費時間。
因為那個測試就是用宣稱實現的,如果不設計嚴謹完整的測試案例,只是測試我把姓名更新成新的值,資料庫撈出來的新值要等於剛剛更新的那個值這類的滿足有宣稱的測試,真的沒有任意實際上意義。
或許唯一有用的,就是提高所謂的代碼覆蓋率與能看到綠色的勾勾那樣。
而要能測出複雜情境的各種可能問題,還要做自動化完整測試案例出來,那個費的工,會是把程式寫出來的好幾倍啊!
吃飽太閒才會去寫自動化測試,可能自動化測試剛寫完需求就改了,然後再重來一遍?
那篇長文的作者就說,碰到自動化測試必須關掉否則程式無法完成編譯執行,原因是需求變了,但沒去重新維護自動化測試的代碼。
簡單說就是瞎。
要不,寫一些套套邏輯,不證自明的檢查規則來湊無意義的測試代碼覆蓋率,要不,就是勞民傷財的寫規劃設計一套測試案例程式來做測試,然後要保佑需求不會變來變去的。
就後者來說,測試程式比實際在使用的程式還複雜,那如果要確保測試程式沒有臭蟲呢?
如果一個公司吃飽太閒又養很多沒事幹的工程師,那倒是沒差,反正這些人平常也沒事吧?
如果測試讓程式碼變得更複雜,怎麼會沒差,一坨程式碼讓工程師看了個個鴨梨山大的,這肯定會增加成本的。
另外,單元測試與自動整合佈署,並不能完全避免系統整合測試的需要。
系統整合測試就不是單元測試的範圍之內的,成本與複雜度也完全不同。
最糟糕的就是,只是儀式上的寫了高覆蓋率的測試代碼,臭蟲還是照樣有,工程師把時間心力放在有做基本單元測試百分之百代碼覆蓋率上頭而不是用心去檢查業務流程有無漏洞那樣,也不把確保運行時可以如預期當成自己的責任,甚至連運行時怎樣才算如預期都不想知道,只想把需求規格照本宣科的實作完,然後單元測試代碼覆蓋率百分之百剩下的都不關我的事。
上面那個句子有多長,有多難讀,那個情境的軟體系統品質大概就有多差,使用者體驗就有多雷吧。
所以簡單的結論是,物件導向設計環境下,單元測試框架是吃飽太閒在用的,成本效益是負的,測試還是很重要。
軟體系統的開發維護,保持簡單才是核心。
針對系統需要的品質,就需要設計合適對應的各種測試,測試還是很重要,至於那種軟體開發框架附帶的單元測試框架,還是洗洗睡吧,想要提高系統品質,別指望那玩意了!