以自動化測試撬動遺留系統(tǒng)
面對遺留系統(tǒng),選擇合適的測試策略,能讓自動化測試的投入在一定時期內(nèi)看到效果,并且建立可持續(xù)進行的機制。同為自動化測試,每種測試在面對遺留系統(tǒng)時遇到的挑戰(zhàn)是不同的,起到的效果也不盡相同。
背景
我目前所服務的企業(yè)大部分系統(tǒng)是遺留系統(tǒng),其中多數(shù)處于相對需求平穩(wěn)階段,即需求并不多,也沒有大需求。但這些系統(tǒng)牽制了和需求所需人力不成比例的大量人力,從系統(tǒng)本身的原因看,有這么幾點。
● 系統(tǒng)晦澀難懂,可讀性可理解性很差。理解原有系統(tǒng)往往占據(jù)了進行一個修改的大部分時間。
● 系統(tǒng)設(shè)計僵化,改動困難,一個小修改,會迫使系統(tǒng)很多部分的改動。
● 系統(tǒng)難以重用,大多軟件單元缺乏可重用性。
● 系統(tǒng)脆弱,引入一個小功能會引入幾個缺陷,修復一個缺陷又引起幾個新缺陷。
投入大量人力,產(chǎn)生的價值卻微乎其微。面對激烈的市場,同質(zhì)化的競爭,成本和質(zhì)量問題日益凸顯。而所謂遺留系統(tǒng),即沒有自動化測試保護的系統(tǒng)。客戶很希望通過引入“自動化測試”來提升系統(tǒng)質(zhì)量,最終幫助他們建立自動化測試機制。
過去幾個月里,我先后投入到幾個遺留系統(tǒng)的自動化測試提升工作中。這些工作都進展不錯,很多系統(tǒng)的核心模塊都有了自動化測試的覆蓋。另外,這次專門針對“遺留系統(tǒng)”所做的自動化測試工作,也帶給我一些新想法:自動化測試,很可能是我們撬動遺留系統(tǒng)的一個支點。
測試策略選擇
圖1是測試金字塔模型,從上至下分別是驗收測試、API測試、集成測試和單元測試。你可以有不一樣的分類,但從上至下,測試粒度越來越小,測試數(shù)量越來越多。一個具備完善自動化測試的系統(tǒng),應該具備類似的測試分布。
圖1 測試金字塔模型
當我們面對的是遺留系統(tǒng)時,追求理想模型肯定是不現(xiàn)實的,那么應該選擇何種測試策略呢?
每 個遺留系統(tǒng)的狀況都不盡相同,可能選擇的策略也會不一樣。但有一點是一致的——所有的測試都不是沒有成本的,在人力并不寬裕的情況下,必須讓測試投入“值 回票價”。而且,必須讓測試投入到短期最有價值的地方,才能讓團隊盡快看到效果和建立信心。我們選擇的策略之一是:快速建立穩(wěn)定性較高的功能測試防護墻, 以此保護系統(tǒng)的核心功能不被任意破壞。這里有兩個關(guān)鍵點。
● 快速,要選擇可以快速建立的測試,讓一定的價值在短期之內(nèi)就能得到體現(xiàn)。
● 穩(wěn)定,指的是測試本身的穩(wěn)定——不因系統(tǒng)變化而對測試產(chǎn)生太大的沖擊,因而維護成本也就相對較低。
這里的功能測試可以是驗收測試、API測試或集成測試,根據(jù)每個系統(tǒng)的情況選擇更好滿足上面兩個關(guān)鍵點的測試類型。
例如,我們曾面對一個Web系統(tǒng),大部分頁面邏輯比較簡單,主要是呈現(xiàn)內(nèi)容;前端通過REST接口跟業(yè)務后臺交互數(shù)據(jù)。剛開始我們選擇基于 WebDriver的驗收測試,但隨后即發(fā)現(xiàn)這類測試編寫成本較高,需要學員掌握較多技能,無法在短期內(nèi)快速為整個系統(tǒng)建立一個防護墻;另一方面,它的穩(wěn) 定性也較低,測試較易受到頁面布局的影響,維護成本較高。在這種情況下,最后我們轉(zhuǎn)而選擇了基于REST接口的測試,因為它的建立成本更低,穩(wěn)定性也更高 (REST接口變化較少),而且也可以覆蓋所有核心功能,相比而言,是個更好的選擇。
我們還選擇了另一個策略:針對熱點區(qū)域(包括需求熱點和缺陷熱點等)添加測試。選擇這些區(qū)域主要基于兩點理由。
● 首先,“非熱點”區(qū)域,也就是暫時穩(wěn)定的區(qū)域,在初期并不是最值得投入為其建立測試的。例如,有個Web系統(tǒng),它有兩個相對獨立的組件,一個是社區(qū),另一個是網(wǎng)店,如果前者是熱點區(qū)域而后者不是,那么前者就更有價值在初期投入建立測試。
● 其次,遺留系統(tǒng)的脆弱性往往體現(xiàn)在“Bug重復出現(xiàn)”、“解決一個Bug,出現(xiàn)兩個Bug”等情形。針對這些活躍區(qū)域添加測試可以對它們起到保護作用,減少出現(xiàn)上述情況出現(xiàn)的機會;同時,也是對這塊區(qū)域的一個重構(gòu)契機。
針對“熱點”區(qū)域,我們一般會在團隊中建立類似“完成新需求必須同時完成測試”、“修復缺陷必須添加測試”這樣的紀律。
同時,選擇合適粒度的測試也很重要。各類測試自己的優(yōu)點,例如集成測試在功能保護上體現(xiàn)效果更快;而單元測試卻會驅(qū)動內(nèi)部質(zhì)量的提升。如果條件允許,選擇多 種粒度的測試結(jié)合,別忘了之前提到的測試金字塔。我們無法為整個系統(tǒng)一下子建立完善的測試,但為某一個區(qū)域,是有可能的。
為遺留系統(tǒng)寫功能測試
功能測試處于測試金字塔的上端,它的穩(wěn)定性相對較低,維護成本也較高。因此寫功能測試一定要關(guān)注提升它的穩(wěn)定性,并降低維護成本,遺留系統(tǒng)在這幾個方面遇到的挑戰(zhàn)可能會更大。
最近我對一個Web系統(tǒng)建立基于WebDriver的功能進行測試,其中面臨的一個很大問題就是HTML頁面缺乏語義、很多元素的定位都得依靠位置等極不可 靠方式,一旦頁某些布局發(fā)生變化,就會影響到測試,維護成本很高。但事情總有兩面性,正是這些測試,讓頁面的重構(gòu)和優(yōu)化得到了團隊的重視。
影響功能測試穩(wěn)定性的另一個重要因素是測試數(shù)據(jù)。對于團隊控制范圍內(nèi)的系統(tǒng),我的建議是隨著測試的建立逐步創(chuàng)建一套可靠的、覆蓋各種典型場景的測試數(shù)據(jù)準備腳本。由此,我們每次都重新建立干凈的測試數(shù)據(jù),讓測試更加穩(wěn)定和可控。
但在遺留系統(tǒng)中,有時會碰上更嚴峻的問題,系統(tǒng)依賴于第三方或其他不在控制范圍內(nèi)的測試系統(tǒng)。功能測試會影響到測試數(shù)據(jù),因此我們的測試很有可能無法重復執(zhí) 行。當然,建立一個測試替身系統(tǒng)是一種選擇方案,但有時并不容易,至少短期之內(nèi)。面對這種情況,我們的解決方案是讓測試程序和測試數(shù)據(jù)解耦。想象一下,如 果同樣的測試由一個測試人員手工執(zhí)行,每次執(zhí)行時不需要選擇相同的數(shù)據(jù),而只需選擇“符合同樣要求”的數(shù)據(jù)。
例如一個電商系統(tǒng),它出售數(shù)量 有限的商品,售完即止。測試數(shù)據(jù)庫中有大量不同商品,但每種商品數(shù)量所剩無幾。如果我們的商品購買測試程序針對某個特定商品,那么在運行幾次之后,商品就 會賣完,測試就不再具備可執(zhí)行性。但測試人員不會這么傻,他每次都可以選擇還有剩余的商品進行購買測試。既然如此,我們的測試程序也同樣可以做到:只要根 據(jù)商品頁面上的信息識別出哪些商品有剩余,隨機或者有策略地選擇其中某個商品進行購買即可。
這樣,我們就讓測試程序和具體的測試數(shù)據(jù)得到了解耦,緩解了測試的不可重復執(zhí)行性,使其更加穩(wěn)定,維護成本也得到降低。除了上述方法,還有其他方式可以避免測試程序和測試數(shù)據(jù)的耦合。
功能測試程序,是在用一種自動化的方式代替人的手工執(zhí)行,但同時也一定程度上丟失了手工執(zhí)行的靈活性。讓功能測試程序保持靈活應變是我們在編寫測試程序時應該考慮的一個重點。
為遺留系統(tǒng)寫單元測試
為遺留系統(tǒng)寫單元測試會面臨和寫功能測試不一樣的挑戰(zhàn),在復雜度及對人的能力要求上也可能會更高一些。原因并不在于測試,而在于遺留系統(tǒng)自身。遺留系統(tǒng)內(nèi)部 的強耦合(依賴)及每個單元的高復雜度使得測試難以開展。例如最近我接觸的幾個遺留系統(tǒng),線程池邏輯和業(yè)務邏輯交織在一起,SQL拼裝邏輯、ORM邏輯和 業(yè)務邏輯也交織在一起,一個方法往往幾百上千行,而且有很深的調(diào)用鏈。
為這樣的系統(tǒng)編寫單元測試,我們普遍遇到了這樣幾個問題。
1、私有方法如何測試:我經(jīng)常被問到的一個問題是“這個私有方法怎么測”?對于私有方法的測試,可能的答案是——不要對私有方法進行測試,只要測試公有方法, 就能覆蓋到私有方法。這個答案可能正確,但在遺留系統(tǒng)中,往往是錯誤的。很多時候,我會反問“為什么要對私有方法進行測試?”
下面這個例子(如圖2所示),是一個有較復雜邏輯的線程。但主要的邏輯存在于組裝和發(fā)起HTTP請求和解析返回的XML上。
圖2 一個有較復雜邏輯的線程
當想對私有方法進行測試時,往往意味著類過于復雜、私有方法承載著太多的職責,通過公有方法覆蓋私有方法的測試成本過高,難度太大。因此,更好的解決辦法是 分離職責、分而治之、單獨測試。通過分離職責,單獨對各部分邏輯進行測試,測試就會簡單很多,如圖3所示;另一個例子如圖4所示。
圖3 單獨對各部分邏輯進行測試
圖4 另一個案例
如果在不改變代碼的前提下為這樣的代碼寫測試,首先要花很多時間理解它,理清各個分支,分別為它們建立復雜的測試準備狀態(tài)等,成本很高,有時甚至為不可能完成的任務。
2、Mock是否是遺留系統(tǒng)單元測試的“銀彈”:當對遺留系統(tǒng)進行單元測試時,Mock作為“銀彈”時不時地出現(xiàn)了。面對遺留系統(tǒng)中有較深依賴鏈的一些方法, 把那些難以建立的依賴統(tǒng)統(tǒng)Mock掉是最快的手段。但經(jīng)驗告訴我:對Mock保持警惕,一旦引入它,就容易被人濫用。當然它本身無錯,錯在使用它的人,如 果一定要怪Mock,就怪有些Mock工具過于強大吧。濫用Mock會導致:
● 測試真實性的減弱,降低測試程序作為測試本身的價值;
● 濫用Mock的“交互驗證(Verification)”,使得測試和實現(xiàn)緊密耦合,降低測試的穩(wěn)定性。
我專門回顧了之前做的幾個系統(tǒng),發(fā)現(xiàn)用到Mock的時候微乎其微,大多使用在對不受控依賴建立測試替身上。在保證測試執(zhí)行速度的前提下,這是我推薦的做法。但面對遺留系統(tǒng)時,我們很容易把“難以建立依賴測試狀態(tài)”作為使用Mock的借口,大量濫用。
單元測試,相對于功能測試等高層次的測試,是代碼級別的白盒測試。但之于它的測試對象而言,我們?nèi)匀粦敯褑卧獪y試視為黑盒測試——單元級別的黑盒測試。例 如對一個排序方法而言,不管它采用什么排序算法實現(xiàn),大多情況下我們只在乎它是否可以將一個無序數(shù)組排序。它的測試也只要基于輸入(無序數(shù)組)和輸出判斷 (有序數(shù)組)即可,就算排序算法改變,測試仍然不受影響。從這個排序方法的角度看,測試對它的內(nèi)部實現(xiàn)不關(guān)心,是黑盒的;當內(nèi)部實現(xiàn)改變時,只要外部行為 不變,測試就不會受到影響。濫用Mock很容易讓測試違反這個特質(zhì)。
此種情況下,我們選擇的方案是:選擇從依賴鏈的末端開始測試,從這里開始逐漸完備測試狀態(tài)準備機制。在保證測試速度(運行速度和編寫速度)的前提下,盡量避免使用Mock。當然,如果你的代碼依賴復雜、混亂,那么首先可能要重構(gòu),重新組織分配職責,簡化依賴關(guān)系。
簡而言之,面對遺留系統(tǒng)進行單元測試時,有幾個值得關(guān)注的實踐:
● 分離職責,分而測之,優(yōu)于私有方法進行測試;
● 逐步完備測試狀態(tài)準備機制,優(yōu)于使用Mock。
之前,我個人對于在遺留系統(tǒng)上實施測試自動化建設(shè)是總體悲觀的:遺留系統(tǒng)龐大,測試的效果并不會在短期內(nèi)得以體現(xiàn);而團隊,恰恰經(jīng)常認為遺留系統(tǒng)足夠穩(wěn)定 (沒有什么大需求,而且90%的代碼可能短期不需要改動),沒有動刀的必要。因此,大多數(shù)團隊很有可能會在看到測試帶來的效果之前就放棄了。
但請相信我,這一切都是假象。故障頻發(fā)的正是這些遺留系統(tǒng):
● 穩(wěn)定是假象,遺留系統(tǒng)里隱藏著很多故障和漏洞,只是暫時沒有爆發(fā)而已;
● 穩(wěn)定是假象,粗劣的設(shè)計讓任何一個需求的變化都會像霰彈一樣影響整個系統(tǒng),遺留系統(tǒng)大都是極其脆弱的。
而單元測試,除了它本身帶來的測試價值之外,對于遺留系統(tǒng)而言,更大的部分在于以下三個方面:
● 驅(qū)動遺留系統(tǒng)的重構(gòu),提升架構(gòu)設(shè)計和內(nèi)部質(zhì)量(遺留系統(tǒng)雖龐大,但壞味道其實都雷同)。這是對遺留系統(tǒng)而言最關(guān)鍵但一般情況下最沒有可能去做的事情;
● 暴露并解決系統(tǒng)中的缺陷和漏洞,在這個過程中,我們發(fā)現(xiàn)并處理了很多漏洞和缺陷,包括多線程處理上、業(yè)務邏輯等;
● 更重要的是提升團隊的重構(gòu)和設(shè)計能力以及團隊質(zhì)量意識,特別是內(nèi)部質(zhì)量。
總結(jié)
面對遺留系統(tǒng),選擇合適的測試策略,能夠讓自動化測試的投入在一定時期內(nèi)看到效果,并且建立可持續(xù)進行的機制。同為自動化測試,每種測試在面對遺留系統(tǒng)時遇 到的挑戰(zhàn)是不同樣的,它們起到的效果也不盡相同。對于遺留系統(tǒng)而言,功能測試是盾,只有它的保護,我們才能放心地對遺留系統(tǒng)動刀;而單元測試是刃,它撬動 的不僅是遺留系統(tǒng),更是遺留團隊。
posted on 2013-02-17 11:33 順其自然EVO 閱讀(259) 評論(0) 編輯 收藏 所屬分類: selenium and watir webdrivers 自動化測試學習