測試你的前端程式碼 – part4(整合測試)

發表於2017-06-05

上一篇文章《測試你的前端程式碼 – part3(端到端測試)》中,我介紹了關於端到端測試的基本知識,從本文介紹整合測試(Integration Testing)。

整合測試

我們已經看過了“測試光譜”中的兩種測試:單元測試端到端測試。實際工作中的測試經常是介於這兩種測試之間的,包括我在內的大多數人通常把這種測試叫做整合測試。

關於術語

和許多 TDD 愛好者聊過以後,我瞭解了他們對“整合測試”這個詞有一些不同的理解。他們認為整合測試是測試程式碼邊界,即程式碼對外的介面部分。

比如他們程式碼中有 Ajax,localStorage 或者 IndexedDB 操作,那其程式碼就不能做單元測試,這時他們會把這些程式碼打包成介面,然後在做單元測試的時候 mock 這些介面。當真正測試這些介面的時候才稱作“整合測試”。從這個角度來說,“整合測試”就是在純的單元測試以外,測試與外部“真實世界”相關的程式碼。

而我和其他一些人則傾向於認為“整合測試”是將兩個或多個單元測試綜合起來進行測試的一種方法。通過介面把與外部相關的程式碼打包到一起,再 mock,只是其中的一種實現方式。

我的觀點裡,決定是否使用真實場景的 Ajax 或者其他 I/O 操作進行整合測試(即不使用 mock),取決於是否能夠保證測試速度足夠快,並且能夠穩定測試(不發生 flaky 的情況)。如果可以確定這樣的話,那儘管用真實場景進行整合測試就好了。不過如果很慢或者發生不穩定測試的情況,那還是用 mock 會好一些。

在我們的例子中,計算器應用唯一的真實 I/O 就是操作 DOM 了,沒有 Ajax 呼叫,所以不存在上面的問題。

mock DOM

這就引出了一個問題:在整合測試中是否需要 mock DOM?重新思考一下上面我說的標準,使用真實 DOM 是否會使測試變慢呢,答案是會的。使用真實 DOM 意味著要用瀏覽器,用瀏覽器意味著測試速度變慢,測試變的不穩定。

那麼是不是要麼只能儘量把操作 DOM 的程式碼分離出來,要麼只能使用端到端測試了呢?其實這兩種方法都不好。還有另一種解決方案:jsdom。一個非常棒的包,用它自己的話說:這是在 NodeJS 中實現的 DOM。

它確實比較好用,可以執行在 Node 環境下。使用 JSDom,你可以不把 DOM 當做 I/O 操作。這一點非常重要,因為要把 DOM 操作從前端程式碼中分離出來非常困難(實際工作中幾乎不可能完全分離)。我猜 JSDom 的誕生就是因為這個原因:使得在 Node 中也可以執行前端測試。

我們來看一下它的工作原理,和往常一樣,需要有初始化程式碼和測試程式碼。這次我們先看測試程式碼。不過正式看程式碼之前請先接受我的歉意。

歉意

這一部分是這個測試系列文章中唯一使用指定框架的部分,這部分使用的框架是 React。選擇 React 並不是因為它是最好的框架,我堅定地認為沒有所謂最好的框架,我甚至認為對於指定的場景也沒有最好的框架。我相信的是對於個人來講,只有最合適,用著最順手的框架。

而我使用著最順手的框架就是 React,所以接下來的程式碼都是 React 程式碼。但是這裡依然說明一下,前端整合測試的 jsdom 解決方案可以適用於所有的主流框架。

ok,現在回到正題。

使用 Jsdom

注意看第 10 – 14 行,首先 render 了 CalculatorApp 元件,這個操作同時也 render 了 DisplayKeypad。第 12 和 14 行測試了 DOM 中計算器的顯示是否是 0(初始化狀態下)。

上面的程式碼是可以執行在 Node 下的,注意到裡面用的是 document。我第一次使用它的時候特別驚訝。全域性變數 document 是一個瀏覽器變數,竟然可以使用在 NodeJS 中。在這簡單的幾行程式碼背後有著大量的程式碼支撐著,這些 jsdom 程式碼幾乎是完美地實現了瀏覽器的功能。所以這裡我要感謝 Domenic Denicola, Elijah Insua 和為這個工具包做過貢獻的人們。

第 10 行中也使用了 document(呼叫 ReactDom 來渲染元件),在 ReactDom 經常會使用它。那麼在哪裡建立的這些全域性變數呢?在測試中建立的,見下面程式碼:

程式碼中建立了一個簡單的 document,把我們的元件掛在一個簡易 div 上。同時還建立了一個 window,其實我們並不需要它,但是 React 需要。最後在 after 中清理全域性變數。

documentwindow 一定要設定成全域性的嗎?濫用全域性變數不論理論和實踐的角度都不是個好習慣。如果它們是全域性的,那這個整合測試就不能和其他的整合測試並行執行(這裡對 ava 的使用者表示抱歉),因為它們會互相覆寫全域性變數,導致結果錯誤。

然而,它們必須要設定成全域性的,React 和 ReactDOM 要求 documentwindow 是全域性的,不接受把他們以引數的形式傳遞。或許等 React fiber 出來就可以了?也許吧,不過現在我們還必須要把 documentwindow 設定成全域性的。

事件處理

剩下的測試程式碼怎麼寫呢,看下面程式碼:

測試中主要實現的是使用者點選 “42 * 2 = ”,結果應該是輸出 “84”。這裡獲取 element 使用的是廣為人知的 querySelector 函式,然後呼叫 click 點選。還可以建立事件,然後手動排程,見下面程式碼:

這裡有內建的 click 函式,所以我們直接使用就好了。就是這麼簡單!

機智的你可能已經發現了,這個測試和前面的端到端測試其實是一樣的。但是注意這個測試要快 10 倍以上,並且實際上它是同步的,程式碼也更容易寫,可讀性也更好。

但是如果都一樣的話,那需要繼承測試幹嘛?因為這是個示例專案嘛,並不是實際專案。這個專案裡面只有兩個元件,所以端到端測試和繼承測試是一樣的。如果是在實際專案中,端到端測試可能包含了上百個單元,而繼承測試只包含少量單元,比如包含 10 個單元。所以實際專案中只有幾個端到端測試,而可能包含了上百個繼承測試。

總結

本文中主要介紹了什麼:

  • 介紹了使用 jsdom 方便地建立全域性變數 documentwindow
  • 介紹瞭如何使用 jsdom 測試應用;
  • 介紹了,測試就是這麼簡單^_^。

相關文章