JavaScript客戶端測試之旅

XfLoops發表於2015-09-28

進行測試是很重要的。測試能讓我們不費勁地擴充套件和重構我們的程式碼。許多開發者遵循測試驅動的開發流程。我相信寫測試能讓軟體開發變得更有趣,並且通常會帶來更好的程式碼。良好設計以及經過測試的系統更容易維護。

在過去的幾年裡,開發者開始把許多的應用邏輯放到瀏覽器中,我們也開始寫越來越多的JavaScript程式碼。因為這種語言非常流行,開發者們開始建立用於改善JavaScript開發體驗的工具。在這篇文章中,我們將談論一些專門用於測試客戶端JavaScript程式碼的工具。

測試設定

我們來討論一下能夠進行測試的工具型別,它們能讓我們構建、分組以及執行我們的測試。

測試框架

框架包含一些函式如suite、describe、test 或 it。這些能讓我們建立測試的分組,這些分組經常被稱為套件(suit)。例如:

我們把應用邏輯分割成塊,每個塊都有自己的套件。該套件包括我們想在程式碼上執行的相關測試。流行的JavaScript測試框架有QUnitJasmine 或Mocha.

宣告庫

我們用宣告庫來做實際的檢查。它們提供了易用的函式,如下面的例子:

有很多的模組可供我們使用。Node.js甚至就有一個內建的模組,也有一些開源的工具供我們選擇,如ChaiExpect 或 should.js

應該提醒的是,一些測試框架擁有自己的斷言庫。

執行器

我們可以需要或者也可以不需要一個執行器(runner)。有些情況下單一的測試框架不能滿足,因而我們需要在一個特定的上下文來執行測試。為了完成這件事,我們使用一個執行器。有些情況下這些工具被稱為“spec runners”或“test runners.”。這些工具包裝了我們的測試套件,並且在一個特殊的環境下執行。我們將會講到這一點。

應用程式

我們需要一個待測試的應用程式。儘管這只是用於舉例說明,但它不能太過簡單。TODOMVC 看起來是個不錯的選擇。它基本上跟其他的TODO app一樣,使用了許多不同的框架。我們用Backbone.js 的變種。這就是該應用程式的樣子:

假設這是我們上個月在做的一個專案,並且計劃下週釋出。我們想確保它通過一些測試,也假設後端的已經測試過了。唯一的問題是客戶端的JavaScript程式碼。在這個時候,由於應用程式已經完成了,我們最感興趣的是它是否能正常地工作。它是一個TODO app,因此我們需要保證使用者能夠新增、刪除以及編輯任務。

在瀏覽器中測試

我們需要對瀏覽器中執行的程式碼執行檢查,因此在瀏覽器中進行測試是很合理的。我們將使用Mocha來作為框架。由於該框架不帶斷言庫,我們將在專案中引用Chai。我們從todomvc.com 下載了app,並且瀏覽了檔案:

這篇文章是關於測試的,因此我們不打算深入Backbone.js工作原理的細節。然而,這裡介紹一下檔案和目錄的基本細節:

  • bower_components – 包含Backbone.js庫、本地儲存助手、jQueryUnderscore.js以及TODOMVC通用檔案
  • js – 這個目錄包含這個app的實際程式碼
  • bower.json – 定義專案的依賴
  • index.html – 包含HTML標籤(模板)

在平常的開發流程中,我們開啟一個瀏覽器、載入應用程式並且使用UI。我們在輸入框敲入了一些文字新建了一個TODO,按Enter鍵然後任務顯示在下面。通過點選小小的X標誌來移除記錄,通過雙擊TODO來編輯任務。我們有很多涉及不同滑鼠事件的動作。

為了測試app,我們不想一遍遍重複上面的步驟。自動化測試可以幫到我們。我們將有效地執行這個應用程式,並且寫能夠與頁面互動就像我們手動操作一樣的程式碼。讓我們建立一個tests_mocha資料夾存放我們的測試。

Mocha和Chai都可以通過npm來安裝,npm來自Node.js。我們只需把它們新增到package.json檔案,並且在同一個目錄下執行npm install。

建立測試執行器

spec.js會包含我們的測試,TestRunner.html 是我們將要修改的副本。我們需要讓應用程式跑起來,因此肯定會用到index.html的程式碼。在討論改變之前先看一看原始檔案是什麼樣的:

客戶端的app會變得相當複雜,我們的一個app需要跑幾個程式。檔案末尾所有的<script>標籤需要包好代表模板的<script>標籤。<section> 也很重要,因此也保留它。

為了看測試的結果需要新增Mocha的樣式,我們只測試JavaScript。因此TODOMVC的base.css可以移除掉,替換頭部中的<link>標籤:

樣式去掉了但是標記仍然在那裡。然而,我們不想讓它可視,因此我們新增另外的CSS類來隱藏它:

有了這兩處變化,我們仍然可以讓專案執行和工作,但它的介面是不可見的。我們有一個空的頁面來等待我們的測試。下面的程式碼在頁面的底部執行,就在TODOMVC的指令碼之後:

空的<div>被模板用來顯示結果,在那之後我們新增Mocha和Chai。最後,我們執行測試。注意,我們的spec.js是在測試框架和宣告庫被初始化時在mocha.run()之前新增的。實際上,瀏覽器會:

  • 匯入Mocha的CSS;
  • 匯入TODOMVC的檔案並且執行應用程式;
  • 匯入Mocha和Chai;
  • 匯入我們的spec.js;
  • 執行測試

編寫測試

準備就緒,讓我們開始寫測試。之前我們提到過要檢查三件事情:使用者能否新增、刪除、編輯待完成任務。

測試從呼叫describe 函式開始,把我們的檢查放在一個測試套件裡。setText 函式是用來改變輸入框的值並模擬按下Enter 鍵。

大多數測試框架允許我們在測試執行之前執行邏輯,這就是我們用before 函式的原因。在本例中,我們需要清空儲存在localStorage 中的資料,因為之後我們期待看到列表中待完成任務的具體數目。

接下來的it 呼叫表示新增、刪除、編輯的三種操作。注意我們用的$ (jQuery)和expect 都是全域性函式。

執行測試

完成這段程式碼,就可以用我們最喜歡的瀏覽器開啟TestRunner.html,並且結果顯示如下:

我們現在能保證我們的專案提供了所需的功能。我們有一個測試並且它是有點自動化的。之所以說“有點”是因為測試在瀏覽器中執行,並且我們仍然需要手工開啟TestRunner.html。

這就是使用這種的問題之一。我們不能強迫開發者一直在瀏覽器中執行測試。測試過程應該是配置或提交過程的一部分。為了達到這個目標,我們需要將測試移到終端。

使用PhantomJS進行測試

下面我們要解決的一個問題是,找到一個能在終端執行的瀏覽器。我們需要一個瀏覽器,因為Backbone.js需要渲染UI並且我們要跟它互動。有一種被稱為沒有介面的(headless) 特殊的瀏覽器,它並沒有一個視覺化介面。

我們通過程式碼來控制它。但是,它們跟真實的瀏覽器功能一模一樣。最流行的一款是PhantomJS。嘗試一下看它怎樣處理我們的測試。

PhantomJS以可執行檔案的形式釋出。換句話說,當成功安裝之後我們有一個命令變數phantomjs 。它適用於幾乎每一個作業系統。同該瀏覽器一同使用,我們將使用Node.js和它的npm。

在終端執行測試

假設我們已經安裝好了PhantomJS,下一步是在終端執行我們的Mocha測試。實際過程中,我們需要在瀏覽器中載入TestRunner.html,檢查來自Mocha框架的結果。我們可以手動操作。但是為了節省時間,我們使用Node.js中的mocha-phantomjs 模組。

快速執行npm install -g mocha-phantomjs會讓mocha-phantomjs可在控制檯中使用。直接將tests_mocha檔案複製到新的目錄tests_mocha-phantomjs下。我們要做的唯一的變動就是將

變為:

模組從測試框架獲得迴應,並將它傳送到終端。這就是結果應該有的樣子:

現在我們的檢查在終端裡執行,我們能新增mocha-phantomjs呼叫到我們的持續整合設定。

PhantomJS很好,但是它在單一的瀏覽器中執行我們的程式碼。如果我們在不同的瀏覽器中測試程式碼呢?特別是針對客戶端的JavaScript,我們應該保證我們的應用程式能在不同的環境中工作。並且不止如此,我們想在真實的瀏覽器中測試。Karma 專案能提供這個功能。

使用Karma作為測試執行器

與mocha-phantomjs類似,Karma 以Node.js模組的形式釋出。但是,這一次我們不僅需要一個模組,還需要其它幾個模組。因此,讓我們按照上述的想法,將tests_mocha拷貝到新的檔案tests_karma裡。package.json檔案應當是這個樣子:

除了上面的包,我們還需要karma-cli。在執行npm install(這會安裝上面列出的依賴)之後再執行npm install -g karma-cli。 karma-cli 有karma 命令,我們可以呼叫它來在終端執行測試執行器。

使用KARMA的問題

我在過去幾個月看了很多有關Karma的東西,並且真的想嘗試它。但是,我發現它被設計專門用於單元測試,而不是整合測試。

執行器的命令列工具接受JSON物件格式的配置,有一些選項,但是沒有一個接受HTML檔案。我們可以傳送JavaScript檔案到瀏覽器,但是我們不能定義要載入的HTML。

有一個Karma使用的上下文模板,它的所有功能是注入JavaScript程式碼和測試。為了使用方便,這是不夠的。我們有HTML標記及<script>中的模板。在執行應用程式之前它們應該已經被載入到頁面中了。

解決方案

我所做的是fork這個專案,並且在配置中增加了一個選項。這可以讓我們設定上下文模板,這也是我為什麼在package.json 檔案中新增一個URL。

保持spec.js不變,把TestRunner.html變為:

需要的<section>和 <footer>都包含在模板中了,最後的程式碼是Karma用於構建最終文件及執行測試。

配置

我提到框架使用了一個配置檔案。使用下面的內容建立一個tests_karma/karma.conf.js檔案:

在這個配置裡,我們列出了要注入到頁面的JavaScript檔案,contextFile 設定是我之前討論過的變化。注意,我們有兩種瀏覽器來執行測試。

執行KARMA

最後一步是在終端執行karma start ./karma.conf.js –single-run。結果如下:

在這一部分的開頭karma-phantomjs-launcher和karma-chrome-launcher。該框架使用兩個模組來執行我們指定的瀏覽器。

因此,我們嘗試在瀏覽器中執行測試,但是這個方法不能擴充套件。我們通過mocha-phantomjs讓其在終端執行,但是那意味只在一個瀏覽器中測試。第三種嘗試是使用Karma作為執行器,開啟兩個不同的瀏覽器,其中一個不是沒有介面的(headless)瀏覽器。最後一個步驟有點複雜,包括一些模組及一個框架補丁。讓我們嘗試另一個執行器——DalekJS

使用DalekJS測試

DalekJS使用一種不同的方法,它不需要測試框架或者斷言,已經自帶了。

安裝非常簡單。我們再一次需要Node.js和npm ,因為這個工具是以Node.js的包釋出的。跟Karma一樣,我們也需要命令列客戶端,以及DalekJS框架本身。

我們建立另一個tests_dalekjs 資料夾,包含package.json 檔案:

當我們安裝好了兩個模組,就可以進行測試了。

編寫測試

好訊息是我們不需要接觸HTML,只需把index.html的檔案路徑拷貝到tests_dalekjs/TestRunner.html檔案中,剩下的步驟都一樣。

因為DalekJS有自己的語法,我們不能像上面的測試使用spec.js檔案。下面是使用DalekJS API所寫的三種操作:

再一次,我們有一個幫助的方法來設定輸入域的值,並且觸發Enter 按鈕。我們匯出的物件包含TODO的新增、刪除、編輯。像這樣設計API是很友好的,因為我們僅通過看方法的執行來知道正在發生什麼。

有一個比較麻煩的方法是執行。它接受一個在瀏覽器上下文下執行的函式。

執行測試

我們使用dalek ./tests_dalekjs/spec.js執行測試,結果:

我們應該記住,DalekJS跟Karma一樣,能夠在Chrome、IE、Firefox 和 Safari下執行。相當多的現代瀏覽器是支援的,我們所要做的是安裝額外的模組,如 dalek-browser-chrome。更多有關支援的瀏覽器,點這裡

Atomus——測試的另一種工具

Atomus 是我工作時使用的工具,上面所有的功能選項都很棒。它們中的大多數經過了大型社群的良好測試。然而,在我看來,它們並非是理想的。

我們能覆蓋使用者的全部過程是很好的,但是通常情況下我們只需要測試應用程式的某一個部分。如果我們只想測試Backbone.js應用的特定檢視呢?使用DalekJS這是很困難的,使用Karma可行但是有點麻煩。模組mocha_phantomjs跟工作方案很接近但是也有一些侷限。

當我們進行單元測試時,我們意識到我們所做的是DOM模擬。在大多數情形下我們對UI不感興趣,而對它的行為感興趣。此時我們找到了jsdom,它是WHATWG和HTML標準的Javascript的實現。

我們建立了一些測試,發現它工作得非常好。它支援DOM操作和DOM事件排程/監聽。甚至支援Ajax請求。Atomus是jadom的包裝器,提供一個強健和友好的API。

建立測試

讓我們以使用Atomus進行的TODOMVC測試來結束這篇文章。我們再一次用到Mocha 和Chai。在新目錄tests_atomus建立package.json,定義依賴:

TestRunner.html跟原始的index.html檔案一樣——唯一引入的外部JS檔案應該移除掉,因為spec,js 中已經引入過了。檔案以下面的程式碼開始:

我們使用檔案系統API來讀取TestRunner.html的內容。browser 變數代表Atomus API。Atomus庫會自動注入jQuery,因此$可以作為快捷符號。第一個測試是這樣:

我們在這裡初始化虛擬瀏覽器、定義外部的js檔案、設定HTML標記的頁面。ready函式接收一個回撥,但頁面和資源全部被載入完成之後會觸發。我們接收一個window物件,跟實際瀏覽器中的window物件一樣。我們可以認為變數是指向瀏覽器的API的,當然也是指向全域性作用域的。

Atomus有一個keypressed 和clicked 的方法模擬使用者互動。我們也可以使用流行的jQuery方法來獲得同樣的結果,但是這些內建的方法可能會帶來一些bugs。

既然我們有了browser 變數,我們能繼續刪除和編輯TODO了。

執行測試

為了執行測試我們不需要命令列客戶端,只需要鍵入mocha ./spec.js ,結果是:

注意,當我們使用Atomus時主要的事情不是執行器或者沒有介面的瀏覽器——它是測試框架。就是它在驅動測試,Atomus只是一個幫助工具。

在我們的案例中,它幫助我們覆蓋不同層次的客戶端架構測試。我們有簡單UI元素的測試,同時使用同樣的工具來整合測試。

為了完成一些有趣的事情,我們看下面的程式碼:

在複雜的環境中,當應用程式的UI跟後端有通訊時,我們需要模擬HTTP請求。在我們的案例中,我們想要涵蓋一個完整的使用過程,但是這實際上變得很複雜,因為我們想要進行單獨的測試。因此,我們模擬傳統的XMLHttpRequest 物件,現在開發者可以通過不同的響應連結到URL了。

結論

測試是很有趣的——特別是當我們有這麼多的工具可供使用的時候。不管我們有著哪種型別的應用,我們應該明確的是有一種測試它的方法。我希望這篇文章能夠幫助你找到合適你的專案的正確工具。

相關文章