七牛前端測試實踐
測試是完善的研發體系中不可或缺的一環,但是在前端開發的項目中做單一測試的相對較少。為讓大家了解前端測試給項目帶來的好處,七牛資深前端工程師馬逸清結合技術開發團隊在前端測試方面的積極探索,特此分享以下前端測試的實踐經驗。
理想中的代碼結構,每個模塊都應該比較簡單,且每個模塊之間的關系也應該非常清晰。但事實上,在做Web開發的過程中,隨著功能和迭代次數越來越多,就會產生一些問題。最初,七牛的Web開發并沒有寫測試,在開發的過程中常遇到代碼稍作修改便會產生Bug的情況。而在做一些新功能開發時又很難察覺到它會對舊功能產生影響,到上報之后才發現有問題。另外,代碼放久了,也不敢重構。因此,七牛開始考慮做前端測試。
七牛前端測試的早期嘗試
七牛最早的嘗試是使用Selenium+Phantom,通過模擬用戶操作來進行測試。比如,登錄過程的代碼大致如下:
測試過程就是打開某個頁面之后,輸入用戶名、密碼,點擊這個按鈕模擬用戶的操作過程。這部分的工作其實是在做端到端的測試,而實際項目中,業務邏輯相對復雜,編寫端到端測試工作量比較大,測試代碼也會很復雜。因此,這樣的做法沒有很好地發揚起來。
既然做完整測試比較麻煩,七牛前端團隊決定做一些更小粒度的測試,對一些基礎函數編寫單元測試。他們會抽取一些基本函數和組件放在統一的模塊中,編寫單元測試去驗證函數是否符合預期。測試粒度變小以后,相比端到端測試,寫測試變得更加容易,但對于DOM依賴比較強的部分,就會難以測試。
舉個例子,設置七牛開發者平臺頂部導航和側邊欄導航選中狀態的功能是通過jQuery 擴展的方式實現的,主要通過匹配URL和導航欄 A標簽中href屬性來判斷是否應該選中。這里的各種子頁面URL設計比較雜亂,非常容易出錯,所以在編寫測試的時候,需要把DOM結構寫進測試腳本。由于一些歷史遺留問題,七牛的導航欄有著兩種結構,因此,這一部分的測試變得非常痛苦,DOM結構發生一些微小的變化,整個測試腳本都要跟著改。所以對于依賴DOM結構的功能測試還比較少,并沒有做到完全覆蓋。
在實際開發中要盡量避免邏輯和DOM的耦合,分離之后才能更加方便地去驗證業務邏輯。使用MVC框架是一個比較好的方式,關注度分離,讓DOM和業務邏輯解耦。
七牛新的項目選擇了AngularJS,AngularJS對測試很友好,不允許在Controller里操作DOM和依賴注入都讓測試工作更加方便。換了AngularJS之后,測試的部分主要分為三類:一是Utils,包括一些格式化的函數、驗證的函數;二是Service里的Model狀態;三是View狀態,這些和具體展現以及業務邏輯都有關,是比較復雜的部分。
Utils的測試
以上是對于Utils測試的代碼,在代碼里注入相關模塊就可以進行測試(網上有很多相關的教程,對于Utils的測試比較簡單)。接下來是做Service的測試,在這個過程中需要做一下設置。如下圖所示:
Service封裝了很多HTTP請求,這樣會依賴于API服務,為此,我們使用AngularJS自帶的Mock模塊將HTTP這一層處理掉,使用Mock模塊在測試時不會發出HTTP請求。同時也可以通過這個模塊驗證發出的請求以及請求的參數等。再接下來是Controller的測試,主要測一些外圍的函數和變量的狀態。如以下代碼:
在實際的測試過程中,團隊會遇到一些依賴不好處理的情況,比如依賴當前時間或者隨機字符串,使用Stub就可以比較好的處理這種情況。如用戶登錄后會記錄登錄的時間,每次請求都會先用當前時間和登錄時間進行比較,判斷登錄是否過期,這里就會依賴當前時間,由于每次運行測試腳本的時間不確定,因此需要替換掉當前時間。七牛使用的是sinon.js,把某個對象當中的某個方法替換掉,一旦每次調用當前時間,它就變成一個固定的值,后面的測試就跟以前的基本方式一樣。具體方法如下:
在做測試的時候,某些邏輯代碼依賴的是外部的返回值,這時用Stub最合適。因為Stub在Sinon庫里可以選擇每一次調用的返回值,或者某個參數的返回值,這樣能滿足絕大部分需求。
Mock除了關注外部依賴的返回值外,還關注外部調用的值。因此,在存/寫一些狀態的時候,沒有返回值就不知道存/寫是否成功,只能測試是否調用到了外部依賴的某個方法,傳進去的參數是否是預期的。使用Mock的方式,最開始調用時要驗證某個Mock對象的某個行為,最后再由Mock對象去驗證它的行為是否正確。手工寫一個Mock的對象會很麻煩,一般會使用通用的一些函數庫來減少工作量。
在測試過程中,大部分情況下用Stub就足夠了。如果一些測試依賴某些返回值,但又沒有返回值,該怎么辦?這里可以用Spy對象去驗證一些函數是否被調用、調用時的參數等,Spy就是帶有行為驗證的Stub。
有了MVC框架和Stub,基本上單元測試中的問題就都能解決了,但是實踐中還存在對于Mock Data處理的小問題。七牛團隊最初的做法是直接在測試里硬編碼了Mock數據,但測試腳本多了以后難以維護,后來便更換了方式,使用了Karma-fixture插件。由于前后端的同時進行,開發時后端API往往沒有準備好,所以這里會先構造一些Mock數據,以Json文件的形式放在Mock目錄下,再運行一個靜態Server專門提供這些Mock數據。使用這樣的插件,在測試代碼里面便可以把這些Mock數據加載進來直接使用。這樣做的好處在于,大家在開發調試和寫單元測試時,使用的是同樣一套Mock數據。將來如果結構發生一些小的調整(如加字段),也只需要做簡單的修改。
目前用到的工具:Karma+PhantomJS
在七牛早期項目中,測試工具主要使用Selenium,但Selenium配置比較復雜。因此,七牛在新項目開始使用Karma+PhantomJS,Karma比Selenium更加輕量,更適合運行單元測試。而腳本運行的瀏覽器環境選擇PhantomJS,可以直接在命令行里看到結果,不會每次運行測試時彈出界面。測試框架官方推薦Jasmine(包括Stub、Spy的功能),雖然Jasmine的斷言以及Stub功能不如Mocha+Chai+Sinon強大,但對于實際項目已經足夠。此外,測試還會依賴一些Karma插件,如測試覆蓋率Karma-coverage工具、Karman-fixture工具及Karma-coffee處理工具。此外,前端社區里提供里比較豐富的插件,常見的測試需求都能涵蓋到。
工具的配置可按照以上兩圖的代碼先把Karma的文件配置好,然后再配置到自己的gulpfile里。配置gulp如下:
gulp主要配置了兩個地方,一是只跑一次測試的testOnce,二是測試代碼的testAndWatch。每次進行修改、保存,它都能自動的再跑,這樣可以一邊開發一邊跑測試,從而查看對當前的代碼有沒有影響。
有了這部分的測試環境,可以把它配置到持續集成的環境當中。由于現有的工具已經非常成熟,這里可以導出覆蓋率報告進行分析,之后還可以改進mockData。如果將來業務變得更加復雜,可以考慮加入UI的自動化測試。
前端測試帶來的益處
首先,寫測試代碼和寫業務代碼時思考方式不一樣,寫測試代碼的時候會考慮業務邏輯的邊際條件是什么,這樣就能提前找到開發時不太容易找到的Bug。當思路還在功能上時,去修改這個Bug會相對比較輕松,如果后面發現再去修改,就會花更多的工夫。
另一個好處是在寫業務代碼的時候,理解會更深刻。寫測試時會發現代碼不好測,輸入輸出不合理的情況。寫測試可以幫助你找到更合理的代碼結構。另外,如果很多人共同維護一份代碼又需要對公共組件做一些小修改的時候,有了測試改起來會非常安心,測試覆蓋比較好的情況下,會減少修改代碼的心理負擔。
前端的框架非常多,大家可以自由選擇適合自己業務的框架。現階段,前端MVC框架發展非常迅猛,它把DOM的結構和業務累計分離開來,使得測試比以前更好做。此外,Node.js的發展對于后端的測試非常重要,它有一些完善的工具鏈,在測試方面不會遇到阻礙,因為搭建一個測試環境已經變得非常簡單。有了以上的測試環境,在寫測試的過程中,更多的便是一個理想和現實之間的平衡。
關于前端測試的建議
對于一些規模較小、人手不足的公司,開發人員寫測試的總會遇到一些影響因素,如:
-
迭代速度快;
-
業務需求不穩定,有時手里的代碼還未寫完,又需要修改功能;
-
人手不足,把功能代碼寫完已經需要加班,但還需要寫測試代碼。
大部分的實際情況比解決方案要復雜。在測試的時候,可能要模擬多種實際情況,而業務代碼則有可能就是實際的解決方案。對于比較大型的產品,測試代碼比業務代碼更多,項目緊張或者功能需求不穩定的情況下,過度測試不不可取。
目前七牛的做法是有選擇性的寫測試。首先,七牛會測試比較重要的部分,評估一些風險。比如數據統計、計費等和用戶密切相關的內容。其次是測試比較穩定的部分,即跟業務不相關的部分函數。再次是測試依賴比較少的部分,這里只需要關注它整個邏輯本身。
在人手不足、時間有限的情況下,可能無法很快加上測試環節,這時一定要養成驗證的習慣。隨著公司業務發展,項目逐漸變大的時候,為了保持代碼的可維護性,進行測試變得非常有必要,特別是在開發過程中,它能幫助你找到一些不容易察覺的小Bug。一些開發人員可能認為寫測試會耗費額外的時間,但是對于大型軟件項目,合適的測試方案能夠大大減少今后的維護成本,從長遠來看,寫測試所花的時間是非常值得的。
來自:http://weibo.com/p/1001603864951990185052