測試你的前端代碼 - part4(集成測試)
上一篇文章《測試你的前端代碼 - part3(端到端測試)》中,我介紹了關于端到端測試的基本知識,從本文介紹集成測試(Integration Testing)。
一、集成測試
我們已經(jīng)看過了“測試光譜”中的兩種測試:單元測試和端到端測試。實際工作中的測試經(jīng)常是介于這兩種測試之間的,包括我在內(nèi)的大多數(shù)人通常把這種測試叫做集成測試。
二、關于術語
和許多 TDD 愛好者聊過以后,我了解了他們對“集成測試”這個詞有一些不同的理解。他們認為集成測試是測試代碼邊界,即代碼對外的接口部分。
比如他們代碼中有 Ajax,localStorage 或者 IndexedDB 操作,那其代碼就不能做單元測試,這時他們會把這些代碼打包成接口,然后在做單元測試的時候 mock 這些接口。當真正測試這些接口的時候才稱作“集成測試”。從這個角度來說,“集成測試”就是在純的單元測試以外,測試與外部“真實世界”相關的代碼。
而我和其他一些人則傾向于認為“集成測試”是將兩個或多個單元測試綜合起來進行測試的一種方法。通過接口把與外部相關的代碼打包到一起,再 mock,只是其中的一種實現(xiàn)方式。
我的觀點里,決定是否使用真實場景的 Ajax 或者其他 I/O 操作進行集成測試(即不使用 mock),取決于是否能夠保證測試速度足夠快,并且能夠穩(wěn)定測試(不發(fā)生 flaky 的情況)。如果可以確定這樣的話,那盡管用真實場景進行集成測試就好了。不過如果很慢或者發(fā)生不穩(wěn)定測試的情況,那還是用 mock 會好一些。
在我們的例子中,計算器應用唯一的真實 I/O 就是操作 DOM 了,沒有 Ajax 調(diào)用,所以不存在上面的問題。
三、mock DOM
這就引出了一個問題:在集成測試中是否需要 mock DOM?重新思考一下上面我說的標準,使用真實 DOM 是否會使測試變慢呢,答案是會的。使用真實 DOM 意味著要用瀏覽器,用瀏覽器意味著測試速度變慢,測試變的不穩(wěn)定。
那么是不是要么只能盡量把操作 DOM 的代碼分離出來,要么只能使用端到端測試了呢?其實這兩種方法都不好。還有另一種解決方案:jsdom。一個非常棒的包,用它自己的話說:這是在 NodeJS 中實現(xiàn)的 DOM。
它確實比較好用,可以運行在 Node 環(huán)境下。使用 JSDom,你可以不把 DOM 當做 I/O 操作。這一點非常重要,因為要把 DOM 操作從前端代碼中分離出來非常困難(實際工作中幾乎不可能完全分離)。我猜 JSDom 的誕生就是因為這個原因:使得在 Node 中也可以運行前端測試。
我們來看一下它的工作原理,和往常一樣,需要有初始化代碼和測試代碼。這次我們先看測試代碼。不過正式看代碼之前請先接受我的歉意。
四、歉意
這一部分是這個測試系列文章中唯一使用指定框架的部分,這部分使用的框架是 React。選擇 React 并不是因為它是最好的框架,我堅定地認為沒有所謂最好的框架,我甚至認為對于指定的場景也沒有最好的框架。我相信的是對于個人來講,只有最合適,用著最順手的框架。
而我使用著最順手的框架就是 React,所以接下來的代碼都是 React 代碼。但是這里依然說明一下,前端集成測試的 jsdom 解決方案可以適用于所有的主流框架。
ok,現(xiàn)在回到正題。
五、使用 Jsdom
- const React = require('react')
- const e = React.createElement
- const ReactDom = require('react-dom')
- const CalculatorApp = require('../../lib/calculator-app')
- ...
- describe('calculator app component', function () {
- ...
- it('should work', function () {
- ReactDom.render(e(CalculatorApp), document.getElementById('container'))
- const displayElement = document.querySelector('.display')
- expect(displayElement.textContent).to.equal('0')
注意看第 10 - 14 行,首先 render 了 CalculatorApp 組件,這個操作同時也 render 了 Display 和Keypad。第 12 和 14 行測試了 DOM 中計算器的顯示是否是 0(初始化狀態(tài)下)。
上面的代碼是可以運行在 Node 下的,注意到里面用的是 document。我第一次使用它的時候特別驚訝。全局變量 document 是一個瀏覽器變量,竟然可以使用在 NodeJS 中。在這簡單的幾行代碼背后有著大量的代碼支撐著,這些 jsdom 代碼幾乎是完美地實現(xiàn)了瀏覽器的功能。所以這里我要感謝 Domenic Denicola, Elijah Insua 和為這個工具包做過貢獻的人們。
第 10 行中也使用了 document(調(diào)用 ReactDom 來渲染組件),在 ReactDom 經(jīng)常會使用它。那么在哪里創(chuàng)建的這些全局變量呢?在測試中創(chuàng)建的,見下面代碼:
- before(function () {
- global.document = jsdom(`<!doctype html><html><body><div id="container"/></div></body></html>`)
- global.window = document.defaultView
- })
- after(function () {
- delete global.window
- delete global.document
- })
代碼中創(chuàng)建了一個簡單的 document,把我們的組件掛在一個簡易 div 上。同時還創(chuàng)建了一個 window,其實我們并不需要它,但是 React 需要。最后在 after 中清理全局變量。
document 和 window 一定要設置成全局的嗎?濫用全局變量不論理論和實踐的角度都不是個好習慣。如果它們是全局的,那這個集成測試就不能和其他的集成測試并行運行(這里對 ava 的用戶表示抱歉),因為它們會互相覆寫全局變量,導致結(jié)果錯誤。
然而,它們必須要設置成全局的,React 和 ReactDOM 要求 document 和 window 是全局的,不接受把他們以參數(shù)的形式傳遞。或許等 React fiber 出來就可以了?也許吧,不過現(xiàn)在我們還必須要把 document 和window 設置成全局的。
六、事件處理
剩下的測試代碼怎么寫呢,看下面代碼:
- ReactDom.render(e(CalculatorApp), document.getElementById('container'))
- const displayElement = document.querySelector('.display')
- expect(displayElement.textContent).to.equal('0')
- const digit4Element = document.querySelector('.digit-4')
- const digit2Element = document.querySelector('.digit-2')
- const operatorMultiply = document.querySelector('.operator-multiply')
- const operatorEquals = document.querySelector('.operator-equals')
- digit4Element.click()
- digit2Element.click()
- operatorMultiply.click()
- digit2Element.click()
- operatorEquals.click()
- expect(displayElement.textContent).to.equal('84')
測試中主要實現(xiàn)的是用戶點擊 “42 * 2 = ”,結(jié)果應該是輸出 “84”。這里獲取 element 使用的是廣為人知的querySelector 函數(shù),然后調(diào)用 click 點擊。還可以創(chuàng)建事件,然后手動調(diào)度,見下面代碼:
- var ev = new Event("keyup", ...);
- document.dispatchEvent(ev);
這里有內(nèi)置的 click 函數(shù),所以我們直接使用就好了。就是這么簡單!
機智的你可能已經(jīng)發(fā)現(xiàn)了,這個測試和前面的端到端測試其實是一樣的。但是注意這個測試要快 10 倍以上,并且實際上它是同步的,代碼也更容易寫,可讀性也更好。
但是如果都一樣的話,那需要繼承測試干嘛?因為這是個示例項目嘛,并不是實際項目。這個項目里面只有兩個組件,所以端到端測試和繼承測試是一樣的。如果是在實際項目中,端到端測試可能包含了上百個單元,而繼承測試只包含少量單元,比如包含 10 個單元。所以實際項目中只有幾個端到端測試,而可能包含了上百個繼承測試。
七、總結(jié)
本文中主要介紹了什么:
- 介紹了使用 jsdom 方便地創(chuàng)建全局變量 document 和 window;
- 介紹了如何使用 jsdom 測試應用;
- 介紹了,測試就是這么簡單。
點擊《測試你的前端代碼 - part4(集成測試)》閱讀原文。
【本文是51CTO專欄作者“胡子大哈”的原創(chuàng)文章,轉(zhuǎn)載請聯(lián)系作者本人獲取授權】