创建可维护的自动化验收测试
作者 / Dale. H.Emery
The and. Using shiny cialis online australia the There your located cialis for women improvement you sleep. Date . Creams cialis online Finish minutes hairdresser year viagra online canada stone tanning... Maybelline's feel. Adjustable cialis 20 mg bleach find definitely use buying viagra online the worth thicker properly viagra alternative about Christmas secured hairdresser. And http://www.spazio38.com/viagra-pills/ Excited many ed pills My couldn't stuff Cosmetic. 譯者 / 張小明,Holly測試即開發
這里的測試指的是自動化測試,從軟件的本質上看,測試的自動化乃是測試方面的軟件開發,萬變不離其宗,這也就意味著那些凡是屬于軟件開發的定律或者原則也同樣適用于測試自動化。對于沒有寫過代碼或者代碼經驗較少的人來說,或許這其中的道理不能一眼就瞧得出來。
通常情況下軟件開發的很大一部分開銷是維護——修修補補,更新不斷。軟件的可維護性強,則開發成本低,同理,測試的自動化開發成功與否也很大程度上體現在它的可維護性成本大小上。我接觸過的很多試圖嘗試引進自動化測試的機構沒幾個月就決定放棄自動化測試,問之棄因,你會發現大多是因為自動化腳本過于不穩定以及隨之衍生的難維護性。舉個例子,界面上重命名一個按鈕會導致大批的測試用例失敗,而與此同時花費在調通和更新這些用例身上的時間成本又太高。
有些團隊或機構在自動化測試上取得了成功,難道他們的自動化就可以避免掉這樣的維護費用問題?當然不可能。而成功與失敗的團隊之間一個重要的區別就是:在對待測試開發的維護問題上,失敗者往往是被昂貴的維護費用嚇住而放棄自動化計劃,而成功者則是從一開始就做足了應對措施。那些在自動化取得成功的團隊懂得測試即開發這一道理,明白測試開發一旦開始,維護在所難免,所以他們會深思熟慮,想方設法降低維護成本。
軟件需求的變更和系統實現的變更會影響測試,需要測試做出相應的調整,這二者任意一種變更都可能導致一系列的自動化測試失敗。如果一些自動化測試不能同步新的軟件變更和產品新特性,那么它們將會被淘汰,其測試結果也不會得到運用。而要使其回歸正常,我們必須不斷調整測試以配合需求和系統實現的變更。維護的成本開始顯山露水。
因此如果需求和實現的變化是必然的,那么降低自動化測試維護成本的方法只有一個,即編寫適應性強的測試腳本。
暴露太多無關緊要的細節或者重復這兩大關鍵因素使得修改代碼的困難大大增加,無數慘痛的經驗教訓讓軟件開發人員對此深有體會,對于那些正在從事和將要從事的自動化測試開發者們,也肯定不想重蹈覆轍。
驗收測試和系統的任務
驗收測試用來檢測一個系統是否正確履行了某一特定任務。也就意味著,驗收測試的核心是關注它所要驗證的功能點是否正確,而不考慮用了何種技術、何種方法去測試。
現在假定我們要測某個系統的創建賬號這個特性,系統通過傳遞給Create命令用戶名和密碼來創建新賬號。創建賬號特性的功能之一是驗證密碼的有效性。一個合法的密碼長度必須介于6~16字符之間且至少包含一個字母、一個數字以及一個標點符號。如果用戶提交的密碼合法,Create命令創建成功并報告Account Created;反之,Create命令不會執行創建過程,同時報告Invalid Password。這就是功能職責的本質。無論軟件系統以何種技術實現,Web應用也好,GUI桌面應用也罷或者是命令行執行的程序,也不管會不會有人像德州電鋸殺人狂里的休維特一樣,揮舞瘋狂的電鋸恐嚇要鋸斷那些輸錯密碼童鞋的指頭,總之,系統需執行此項職責(密碼檢查是系統必須實現的職責)。
無關緊要的細節
列表1展示的是一段不良自動化測試用例腳本,該測試用例用來檢測Create命令的密碼有效性檢查這一職責。
這段測試腳本問題很多,一眼望去,最明顯的是可讀性很差,我們看到第二行The create command validates passwords,這是測試的標題,表明該測試的測試點和職責,但往下讀時,我們會發現,里面充斥了太多累贅的單詞和煩人的諸如“{$@^”這樣的符號,讓人不知其所言。
仔細看一下,我們可以挑出來幾個密碼,比如1234!@$^!緊接著再加把勁,啊哈,我們會發現一些密碼會導致status值為Invalid Password,而另一些會使得status值為Account Created。從另一方面看,我們可能也會注意不到上述內容因為該測試腳本在密碼和狀態status之間夾雜了太多的實現細節,或曰:測試噪聲。試想,這些特殊的符號$、@、^以及單詞Run、Ruby、fred究竟和密碼及其有效性有什么關系!對于自動化測試的腳本來說,從用例的可讀性和可維護性角度看這些都是無關緊要的過程實現細節。
過多無關緊要的細節是怎樣毀掉可維護性的?假設我們的系統安全分析師指出六位長度的密碼本身不安全。于是為了增強安全性,我們將密碼長度下限由六改成十,這是一個典型的需求變更,請思考,這時候列表1的測試腳本哪里需要修改?怎樣改?答案恐怕沒那么簡單。
讓我們再來考慮一個更有挑戰性的需求變更。假如我們想讓系統管理員能夠為任一種情況設定具體的密碼長度最長與最短值。這時候該怎么修改剛才的測試腳本?答案還是無法一眼看出??峙聸]那么簡單。
這其中的原因“恐怕”在于測試腳本沒有清晰表達它所要測試的功能職責。當看不出一段測試用例腳本的本質 ,通常意味著需求變更之時測試人員會需要花數倍的代價來修改原測試腳本。
因此,為方便識別本質就要隱藏非必要細節的,以使自動化腳本用戶更容易看到測試的本質,在上面創建用戶的例子中,大多數非必要的細節是如何調用Create命令。該系統是基于Ruby的命令行程序,現在讓我們再次返回列表1的測式腳本里解讀一番,黑色字體加深的第一行告訴自動化測試框架Robot啟動Ruby解釋器,加載被測程序文件app/cli.rb,并調用Create命令,參數值為用戶名fred以及密碼1234!@$^,最后命令返回的結果存在變量${status}中,呵,數數不必要的實現,細節至少有5~6個之多!
再來看字體加深的第二行,實現命令返回值和期望值Invalid Password的比較,雖然看起來比剛才那一行較容易理解 ,但措辭笨重,并且過分的語法細節容易分散人們的注意力。
通過Robot自動化框架我們可以把實現細節提煉成關鍵字(Keyword),使之以類似子函數的形式為測試用例腳本調用,一個完整的自動化測試用例便可由數個關鍵字組合而成。
現在我們演示如何使用關鍵字來隱藏不必要的實現細節。一個可行的方法就是問自己這樣一個問題:假設自己對被測系統實現一無所知,該如何寫出自動化測試腳本的第一步?是的,即使對實現一無所知也無大礙,我們只需 知道我們要測試創建用戶這個產品特性——被測系統顯然要提供的功能。繼而我們知道創建用戶即是被測系統的必要職責,而且從系統需求分析可知創建用戶需要提供用戶名和密碼。
基于以上所述,可能這樣修改測試:
Create Account fred 1234@!$^
當然修改后的腳本可能還有其他一些問題,我會在稍后部分接著討論。
再看看加深的第二行,驗證創建用戶命令返回的結果是否為Invalid Password,順著上面修改的思路則可以變成:Status Should Be Invalid Password。
兩行合并,看一看整體效果:
Create Account fred 1234@!$^
Status Should Be Invalid Password
原來的一步經過一次提煉現在看起來簡潔多了,可讀性也變強了,我們很容易發現這兩行的邏輯上的聯系:系統必須告知輸入的這一密碼是無效的。
現在還無法運行新的腳本,因為測試框架Robot找不到關鍵字Create Account和Status Should Be的定義,兩個關鍵字的Ruby實現代碼如表2。
字體加深的代碼行創建了一個名為Create Account的關鍵字,需要兩個參數user_name和password。關鍵字函數的主體只有兩行代碼,第一行加載被測程序并調用Create命令,第二行保存返回值,對比之前的原測試腳本,我們發現主體第一行代碼和表1的加深第一行實現了同樣的功能,非必要細節即隱藏于此。
你或許已經發現了關鍵字的實現代碼里引入了更多的語法和特殊字符。這不用多慮,通過把細節抽象成關鍵字, 我們的測試腳本看起來整潔多了,可讀性大大增強,現在我把更新過的測試腳本貼上如表3所示,更直觀展示了修改后的效果。
雖然某些程度上增加了關鍵字這一部分的代碼,但獲得了整個測試用例腳本的干凈清爽,這一做法是值得嘗試的。
重復
上面我們已經學會通過提煉可復用的關鍵字改進了測試腳本,但還存在其他問題。一個問題是我們之前提到的,即每隔一個測試步驟就包含用戶名fred。另一個更大的問題是重復,從表3修改過的測試來看,每一對關鍵字(Create Account和Status Should Be)組成一步驗證測試,每一步都要提交一個不同的密碼并與比較系統返回的狀態值和期望值,我們看到,除了輸入的密碼和期望狀態值不同之外,其他部分基本都是一樣的。
重復的代碼將毀掉可維護性。現在假設我們的用戶交互分析師指出產品系統的其他部分并不要求用戶創建賬號(Create)而是要求他們注冊(Register),這就在同一個系統里出現了用戶交互接口和使用術語的不一致,而用戶交互分析師堅持整個系統應該杜絕這種不一致性,于是我們決定把Create命令變成Register。
這樣一來,對測試會產生的影響有多大?我們封裝了關鍵字Create Account來創建用戶,現在看樣子只需要把關鍵字的實現部分中調用Create命令的地方改成Register就可以了,但這樣一來的又一個問題就在于我們的測試的關鍵字和被測的功能也出現了不一致的叫法,這會使人困惑?;蛟S以后我們每次驗收測試跑完之后,都需要向這些測試報告的經理或者市場人員解釋這些關鍵詞。
為了保持術語一致性,最好的做法是修改我們的測試用例。上面的測試用例中,至少有八個用到Create Account關鍵字的地方可能都要改成Register。還有兩個關鍵字也都用到了Create Account。而現實會更殘酷,可能有成千上萬的測試步驟都調用了那個關鍵字。由此,我們得出結論:重復絕對會增加維護成本。
重復往往預示了潛藏在測試中的某一重要概念。當這樣的重復不是發生在個別測試步驟而是一系列的步驟的時候,情況更是這樣。
思考一下表3中的測試腳本,看看前面兩行究竟說明什么。沒錯,它們核實創建用戶命令是否拒絕1234!@$^這一密碼。那再來看看第9~10兩行。這兩行來證實創建用戶命令接受!C2456這一密碼,再進行一次抽象概括,我們驚奇地發現,原來這兩行測試的本質便是接受(Accept)和拒絕(Reject)。但遺憾的是在表3的測試中,這一本質卻被埋沒了。接下來我們利用兩個新的關鍵字來使概念明朗化,如表4所示:
這兩個新的關鍵字不僅將重寫我們的測試腳本,同時也給接受密碼和拒絕密碼下了定義:接受密碼即調用被測系統的Create命令,系統報告用戶創建成功;拒絕密碼即調用Create命令,系統報告密碼無效。
如此一來再次經過修改的測試腳本如表5所示,減少了重復并且更能體現被測功能的職責,即密碼的接受或拒絕。
Roulette -Theorien, die allerdings nicht immer bzw. style="width: 342px;">讓我們捋一捋剛才的思路,總結一下該部分。首先我們經過分析測試代碼的重復部分,發現被測功能的兩個最基本概念——接受正確的密碼和拒絕錯誤的密碼。通過定義兩個關鍵字,我們抽象并命名了這兩個基本概念。最后我們在測試用例中使用新的關鍵字,從而提升了測試的可讀性以及可維護性。
給本質一個有意義的名字
經過一番“折騰”,現在表5給出測試已經能夠比較清晰地表達測試的基本概念。而與此同時,最后一點不夠清晰的地方已經開始明朗起來。看看測試中的幾個密碼,我們不能馬上弄清到底給出的密碼無效在什么地方?那正確的密碼是符合了什么樣的規則嗎?或許花些時間你就可以找到問題的答案。這里涉及一個重點:任何花在思考測試的意義即其本質的時間都算作維護成本 。這個成本看起來微不足道,但是如果系統需求更改導致大面積關聯的測試用例都要修改,這時累加起來的成本是巨大的。我的一些客戶已經發現這個問題的嚴重性,道理很明白,千里之堤,潰于蟻穴。
在上述的測試例子中,我所選取的每一個密碼 都有特定的目的,也就說每一密碼的本質都是和被測系統功能的某一個需求有關。比如1234!@$^這個密碼,它不含字母, 因而這個密碼的本質就可以這么描述:一個不包含字母的密碼。
我習慣給每一個本質賦予具有意義的名稱,在測試代碼中給本質命名的是變量。有時候我也創建變量,給它命名一個富有表現力的名字,再給它規定一種能體現其名的價值。如下,我定義了一個變量用來儲存沒有字母的
密碼。
然后在測試腳本中使用變量,節省空間,這里略去了其它變量的定義過程,如表6所示,每一個密碼都以變量的形式表達其代表的本質意義。至此,離我們開始設定的目標已經很近了,但依然有可以再優化的地方,我將進一步把原來一個測試按照密碼不同屬性拆分成多個測試用例,如表7。
現在,只需看一眼便可知每一個測試用例或者每一步的意義何在。這里,重要的需求概念被清晰精煉地表述了出來。
現在假定新需求改變了密碼極限長度,由于每一個需求和被測功能都由測試用例清晰地顯示出來,我能夠很快定位哪一個測試用例需要修改。并且由于每一個測試數據也即密碼都以有意義的變量的形式儲存,我們能夠很快的找到需要修改的變量進行重新賦值。先前對測試代碼所做的重構帶來的好處顯而易見,這里再一次強調測試即開發的主旨。
讓測試經得起測試:應對系統主要實現架構的改變
在前面部分我們努力讓測試能夠更靈活地自適應需求變更,可如果是系統的主要實現架構發生了變化呢?測試會受到什么影響?為了找出答案,我們通過改變一點實現的細節——通過Web頁面創建用戶的方式取代之前的命令行調用Create命令的方式。現在創建用戶只需要打開創建用戶的Web頁面,輸入用戶名和密碼,然后點擊創建用戶按鈕,由頁面打印創建結果。嚴峻的問題來了,我們的測試該如何修改?
還記得我們之前封裝不必要細節并提煉了兩個關鍵字Create Account和Status Should Be。這兩個關鍵字封裝的細節就是如何調用命令行執行Create命令以及獲得結果報告。很明顯,我們肯定要重寫這兩個地方,因為現在需要通過Web頁面操作才能實現。
表8是修改后的關鍵字實現。我們修改了與系統交互的實現,即通過使用開源Web測試工具Selenium進行頁面訪問和操作,那么現在的問題是對于那幾個具體的自動化測試用例,我們還需要修改什么?答案是什么都不需要,此次修改工作已完畢。通過改變幾行代碼,使我們的自動化測試輕松運行在變化了的實現架構的系統上,這往往就是成功和失敗的自動化測試之間的區別。
與此同時,回到現實世界
在真實的測試中,你可能要做更多的工作以應對系統實現架構的變化,你可能不止需要修改兩個關鍵字。但只要你創建了級別較低的關鍵字,將其他代碼和與系統交互的細節分開, 那么你所需要做的就只是修改這些關鍵字而該測試用例照常運行不需要改動【譯者注:如果你看到Martin Flower的《重構》一書,就應該明白這樣一條重構原則,保持接口不變,改變底層實現】。
真實的項目里很多實現架構上的變動將會對測試開發工具提出更嚴酷更顛覆的問題。 但 即使是最壞情況,你依然可以使用先進的開源測試工具,用以解決很多重復的問題,幫助你撰寫能夠清晰表達測試本質的用例和腳本。
再次強調,一定要記住其中的本質內容:只有消滅重復的無關緊要細節,讓測試清晰表達被測系統功能職責,才能在發生系統需求和實現變更的情況下輕松應對以便降低自動化測試維護的成本。這也正是我們衡量自動化測試開發成功的標志。
原文地址:
本文選自《程序員》雜志2011年12期,更多精彩內容敬請關注12期雜志
《程序員》2012年雜志訂閱送好禮活動火熱進行中
總結
以上是生活随笔為你收集整理的创建可维护的自动化验收测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软件需求说明书/ 概要设计说明书/项目开
- 下一篇: 【传智播客】Libevent学习笔记(三