當前位置:
首頁 > 最新 > 用 jest 單元測試改善老舊的 Backbone.js 項目

用 jest 單元測試改善老舊的 Backbone.js 項目

對於早期的前端 SPA 項目,Backbone.js + Require.js 是一種常見的技術組合,分別提供了基礎的 MVC 框架和模塊化能力。

對於這樣的既有項目,在之前的文章中也進行過分析,常常面臨依賴不清、封裝混亂,以及缺乏測試等問題;對之進行維護和新需求開發時,結合其本身特點,在 TDD 的方式下進行漸進的改善,而非推倒重來,無疑是個可行的辦法。

本文將嘗試用一個重構實例來拋磚引玉,講解如何對其應用較新的 jest 測試框架,並用 ES6 class 等新手段升級 Backbone.View 視圖組件和改善頁面結構,希望能對類似項目的改善起到開啟思路的作用。

關於測試、重構的各種概念,不再重複介紹;請先參閱以下文章:

Backbone.js / Require.js 技術棧回顧


首先說 Require.js,在沒有 webpack 的日子裡,這是最常見的模塊化管理工具。

其本身可以提供 AMD 規範的 JS 模塊,並提供了通過插件載入文本模板等能力。

在實際的項目中,我們採用了 ES6 語法和 ESM 模塊規範來編寫源文件,並藉助 babel 將其轉譯為 UMD 模塊;最後通過 Require.js 提供的優化工具 來打包,並由 Require.js 本身在瀏覽器里實現模塊的載入。

當然,採用 ES6語法 和 babel 並非一定必要,AMD 也是可以實現測試的。


不同於提供整套方案的 Angular 的是, Backbone.js 提供了一個非常基礎和自由的 MVC 框架結構,不僅可以用多種方式組織項目,也可以自由替換其中的某一部分。

其主要功能模塊包括:

Events:提供一系列事件的綁定和觸發等功能

Model: 對數據或狀態的轉化、校驗、計算派生值、提供訪問控制等,也負責數據的遠程同步等,並有事件觸發機制;作用類似於 MobX

Collection: Model 的集合

Router: 提供了 SPA 的前端路由功能,支持 hashChange 和 pushState 兩種方式

Sync: 一些遠程請求的方法

View: 可以拼裝模板數據、綁定事件等的視圖組件

在我們的實際項目中,視圖層同時支持了 Backbone.View 和早期的 react@13,這也正體現了其靈活之處。

通常的 Backbone 項目也可以忽略文中涉及 react 的部分。

升級測試框架

和之前文章中的例子相同,本次依然採用 Jest 作為測試框架。


早期的項目中其實是有一些單元測試代碼的,主要是用 Jasmine 對部分 model/collection 進行了測試。由於 Jest 內置了 Jasmine2,所以這部分的語法問題不大,基本可以無痛遷移。

早先測試的主要問題在於:

一是沒有整合到工作流中,採用單獨的網頁作為載體,久而久之就會遺忘這個步驟,用例可能失效,新加入的團隊成員也不會注意到這項工作的存在

二是當時對 model/collection 的單元測試並不嚴謹,依賴了提供 mock 數據的 php 伺服器環境

三是由於視圖層沒有很好的組件化,從而缺乏對視圖組件的測試


jest 是比較新的測試框架,默認零配置,但也提供了靈活的適配方法,可以適應各種項目,包括 Backbone.js 的情況。

這位 @captbaritone 小哥提供了一個很好的講解視頻 (需要科學上網 https://www.youtube.com/watch?v=BwzjVNTxnUY&t=15s),並且配上了實例代碼(https://github.com/captbaritone/tdd-jest-backbone)。


配置兩種 npm script,分別用於開發時實時運行測試和 build 時運行測試

目標項目中,其實是用 babel 5 做的 ES6 轉譯;但是由於之前的源代碼已經全部採用了 ES6 語法開發(部分初始 AMD 代碼也做過自動轉化),所以我們完全可以在測試時採用較新的 babel 6


根據目標項目的情況採用了 enzyme-adapter-react-13 做適配

用 cross-env 設置環境變數 test,從而配置出適用於 jest 的 .babelrc 文件,且不影響生產環境

根據項目中的具體情況,按原來的規則做好組件名稱的映射


如果只寫好了測試,而單獨存在,只能用 執行的話,那就重蹈了原來的覆轍;這裡藉助 插件將其加入已有的 工作流:

這樣在之後的 build 任務中,一旦有單元測試未通過,整個流程將停止執行。

測試 model 和 collection

一個 model 大概長這個樣子:


可以發現 model 中依賴了以個全局變數中的屬性

首先編寫一個假的全局對象:

測試套件中,在 model 之前引入這個模塊就可以了:


搞定了非同步請求的地址,自然要攔截真正的請求;

Backbone 中的請求,包括 Backbone.sync / model.fetch() 等, 本質上還是調用的 jQuery 中的 方法(默認情況下),也就是傳統的 xhr 方式,使用 sinon 就可以很好的勝任這種暗度陳倉的工作:


調用 Backbone.Model 實例的 isValid() 方法,會得到數據是否有效的布爾值結果,同時觸發內部的 validate() 方法,並更新其 validationError 的值;利用這些特性,我們可以做如下測試:

collection 的測試和 model 相比並無特別,不再贅述

view 之必然的 testable 組件化

開篇提到過,項目中以前的過時測試用例中,是缺少 view 視圖層部分的。

這一方面是囿於當時測試意識的不足,更主要的原因是沒能很好解決組件化的問題。

要對 view 進行測試,就得將其拆分重構為功能明確、便於復用的各種小型組件。


首先進行的,是類似於 React.createClass 向 class extends Component 的進化,Backbone.View 也是可以華麗轉身的。

傳統的 view 寫法是這樣的:

採用 ES6 class 的寫法,則可能是:

目標項目的很多頁面,沒有合理的封裝出子組件,而僅僅是把需要復用部分的 html 提取成模板,在本頁面「拼裝」多個子模板,或和其他頁面復用。這部分歸因於 Backbone 的「過分自由」,官網或者當時的通用實踐中並未給出很好的組件化方案,只是停留在用依賴的 underscore 實現 _.template() 的階段。

另一個難點在於,Backbone.View 的 constructor / initialize 「構造函數」中,並不能接受自定義的 props 參數。

解決的辦法是進行一定的外層封裝:

也可以「繼承」一個 View:

在頁面中使用時,先傳參獲取到真正的 Backbone.View 組件:

再手動調用其 render() 方法並加入頁面視圖的 DOM 中:

這樣就在很大程度上實現了 Backbone.View 組件的封裝和嵌套。

測試 Backbone.View 組件

比之於測試 react 還需要 enzyme 等的支持,測試 Backbone.View 其實要簡單許多,只需要獲取到其 $el 屬性,調用 jQuery 的慣有方法即可:


自然還是用 sinon 來做:

Backbone.js + Require.js 在測試中的一個小問題是:頁面或組件中一般會用 text.js 組件引入模板,其 ES6 形式為:

因為測試環境沒有 require.js 或者 webpack 的加持,我們只能想辦法將其劫持,並將正確的結果注入對應的測試模塊中;

要實現這一目的,就要用到 方法,其缺點是用了這個就不能用 ES6 的 import 語法了,配置和使用簡要說明如下:

總結

jest 靈活的配置能力,使其能方便的應用於各種類型既有項目的 TDD 開發和重構

之前的其他測試框架下的用例,可以快速遷移到 jest 中

Backbone.View 視圖組件在經過 ES6 升級和合理封裝後,可以明顯改善頁面的整潔度,並順利應用於單元測試

可以用 sinon.createFakeServer() 攔截 Backbone.Model 中的非同步請求

原來用 Require.js 下的 text.js 組件引入的模板,也可以用 jest.doMock() 很好的支持

將單元測試任務加入原有的 build 工作流,可以保證相關代碼之後的持續有效

(end)

-------------------------------------

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 微生活前端開發 的精彩文章:

TAG:微生活前端開發 |