做自(zì)由與創造的先行者

測試

Vue.js中文(wén)手冊

爲什(shén)麽需要測試 ​

自(zì)動化測試能(néng)夠預防無意引入的 bug,并鼓勵開(kāi)發者将應用(yòng)分解爲可測試、可維護的函數、模塊、類和(hé)組件。這(zhè)能(néng)夠幫助你(nǐ)和(hé)你(nǐ)的團隊更快(kuài)速、自(zì)信地構建複雜(zá)的 Vue 應用(yòng)。與任何應用(yòng)一樣,新的 Vue 應用(yòng)可能(néng)會(huì)以多種方式崩潰,因此,在發布前發現(xiàn)并解決這(zhè)些(xiē)問題就變得十分重要。

在本篇指引中,我們将介紹一些(xiē)基本術語,并就你(nǐ)的 Vue 3 應用(yòng)應選擇哪些(xiē)工(gōng)具提供一些(xiē)建議(yì)。

還有一個特定用(yòng)于 Vue 的小(xiǎo)節,介紹了(le)組合式函數的測試,詳情請(qǐng)參閱測試組合式函數。

何時(shí)測試 ​

越早越好(hǎo)!我們建議(yì)你(nǐ)盡快(kuài)開(kāi)始編寫測試。拖得越久,應用(yòng)就會(huì)有越多的依賴和(hé)複雜(zá)性,想要開(kāi)始添加測試也(yě)就越困難。

測試的類型 ​

當設計(jì)你(nǐ)的 Vue 應用(yòng)的測試策略時(shí),你(nǐ)應該利用(yòng)以下(xià)幾種測試類型:

單元測試:檢查給定函數、類或組合式函數的輸入是否産生預期的輸出或副作(zuò)用(yòng)。

組件測試:檢查你(nǐ)的組件是否正常挂載和(hé)渲染、是否可以與之互動,以及表現(xiàn)是否符合預期。這(zhè)些(xiē)測試比單元測試導入了(le)更多的代碼,更複雜(zá),需要更多時(shí)間來(lái)執行。

端到(dào)端測試:檢查跨越多個頁面的功能(néng),并對(duì)生産構建的 Vue 應用(yòng)進行實際的網絡請(qǐng)求。這(zhè)些(xiē)測試通常涉及到(dào)建立一個數據庫或其他(tā)後端。

每種測試類型在你(nǐ)的應用(yòng)的測試策略中都發揮着作(zuò)用(yòng),保護你(nǐ)免受不同類型的問題的影響。

總覽 ​

我們将簡要地讨論這(zhè)些(xiē)測試是什(shén)麽,以及如何在 Vue 應用(yòng)中實現(xiàn)它們,并提供一些(xiē)普适性建議(yì)。

單元測試 ​

編寫單元測試是爲了(le)驗證小(xiǎo)的、獨立的代碼單元是否按預期工(gōng)作(zuò)。一個單元測試通常覆蓋一個單個函數、類、組合式函數或模塊。單元測試側重于邏輯上(shàng)的正确性,隻關注應用(yòng)整體功能(néng)的一小(xiǎo)部分。他(tā)們可能(néng)會(huì)模拟你(nǐ)的應用(yòng)環境的很(hěn)大(dà)一部分(如初始狀态、複雜(zá)的類、第三方模塊和(hé)網絡請(qǐng)求)。

一般來(lái)說,單元測試将捕獲函數的業務邏輯和(hé)邏輯正确性的問題。

以這(zhè)個 increment 函數爲例:

js

// helpers.js

export function increment (current, max = 10) {

if (current < max) {

return current + 1

}

return current

}

因爲它很(hěn)獨立,可以很(hěn)容易地調用(yòng) increment 函數并斷言它是否返回了(le)所期望的内容,所以我們将編寫一個單元測試。

如果任何一條斷言失敗了(le),那麽問題一定是出在 increment 函數上(shàng)。

js

// helpers.spec.js

import { increment } from './helpers'

describe('increment', () => {

test('increments the current number by 1', () => {

expect(increment(0, 10)).toBe(1)

})

test('does not increment the current number over the max', () => {

expect(increment(10, 10)).toBe(10)

})

test('has a default max of 10', () => {

expect(increment(10)).toBe(10)

})

})

如前所述,單元測試通常适用(yòng)于獨立的業務邏輯、組件、類、模塊或函數,不涉及 UI 渲染、網絡請(qǐng)求或其他(tā)環境問題。

這(zhè)些(xiē)通常是與 Vue 無關的純 JavaScript/TypeScript 模塊。一般來(lái)說,在 Vue 應用(yòng)中爲業務邏輯編寫單元測試與使用(yòng)其他(tā)框架的應用(yòng)沒有明(míng)顯區(qū)别。

但(dàn)有兩種情況,你(nǐ)必須對(duì) Vue 的特定功能(néng)進行單元測試:

組合式函數

組件

組合式函數 ​

有一類 Vue 應用(yòng)中特有的函數被稱爲 組合式函數,在測試過程中可能(néng)需要特殊處理(lǐ)。 你(nǐ)可以跳轉到(dào)下(xià)方查看(kàn) 測試組合式函數 了(le)解更多細節。

組件的單元測試 ​

一個組件可以通過兩種方式測試:

白(bái)盒:單元測試

白(bái)盒測試知(zhī)曉一個組件的實現(xiàn)細節和(hé)依賴關系。它們更專注于将組件進行更 獨立 的測試。這(zhè)些(xiē)測試通常會(huì)涉及到(dào)模拟一些(xiē)組件的部分子組件,以及設置插件的狀态和(hé)依賴性(例如 Piana)。

黑盒:組件測試

黑盒測試不知(zhī)曉一個組件的實現(xiàn)細節。這(zhè)些(xiē)測試盡可能(néng)少地模拟,以測試組件在整個系統中的集成情況。它們通常會(huì)渲染所有子組件,因而會(huì)被認爲更像一種“集成測試”。請(qǐng)查看(kàn)下(xià)方的組件測試建議(yì)作(zuò)進一步了(le)解。

推薦方案 ​

Vitest

因爲由 create-vue 創建的官方項目配置是基于 Vite 的,所以我們推薦你(nǐ)使用(yòng)一個可以利用(yòng)同一套 Vite 配置和(hé)轉換管道(dào)的單元測試框架。Vitest 正是一個針對(duì)此目标設計(jì)的單元測試框架,它由 Vue / Vite 團隊成員開(kāi)發和(hé)維護。在 Vite 的項目集成它會(huì)非常簡單,而且速度非常快(kuài)。

其他(tā)選擇 ​

Peeky 是另一速度極快(kuài)的單元測試運行器,對(duì) Vite 集成提供第一優先級支持。它也(yě)是由 Vue 核心團隊成員創建的,并提供了(le)一個基于圖形用(yòng)戶界面(GUI)的測試界面。

Jest 是一個廣受歡迎的單元測試框架,并可通過 vite-jest 這(zhè)個包在 Vite 中使用(yòng)。不過,我們隻推薦你(nǐ)在已有一套 Jest 測試配置、且需要遷移到(dào)基于 Vite 的項目時(shí)使用(yòng)它,因爲 Vitest 提供了(le)更無縫的集成和(hé)更好(hǎo)的性能(néng)。

組件測試 ​

在 Vue 應用(yòng)中,主要用(yòng)組件來(lái)構建用(yòng)戶界面。因此,當驗證應用(yòng)的行爲時(shí),組件是一個很(hěn)自(zì)然的獨立單元。從(cóng)粒度的角度來(lái)看(kàn),組件測試位于單元測試之上(shàng),可以被認爲是集成測試的一種形式。你(nǐ)的 Vue 應用(yòng)中大(dà)部分内容都應該由組件測試來(lái)覆蓋,我們建議(yì)每個 Vue 組件都應有自(zì)己的組件測試文(wén)件。

組件測試應該捕捉組件中的 prop、事(shì)件、提供的插槽、樣式、CSS class 名、生命周期鈎子,和(hé)其他(tā)相關的問題。

組件測試不應該模拟子組件,而應該像用(yòng)戶一樣,通過與組件互動來(lái)測試組件和(hé)其子組件之間的交互。例如,組件測試應該像用(yòng)戶那樣點擊一個元素,而不是編程式地與組件進行交互。

組件測試主要需要關心組件的公開(kāi)接口而不是内部實現(xiàn)細節。對(duì)于大(dà)部分的組件來(lái)說,公開(kāi)接口包括觸發的事(shì)件、prop 和(hé)插槽。當進行測試時(shí),請(qǐng)記住,測試這(zhè)個組件做了(le)什(shén)麽,而不是測試它是怎麽做到(dào)的。

推薦的做法

對(duì)于 視(shì)圖 的測試:根據輸入 prop 和(hé)插槽斷言渲染輸出是否正确。

對(duì)于 交互 的測試:斷言渲染的更新是否正确或觸發的事(shì)件是否正确地響應了(le)用(yòng)戶輸入事(shì)件。

在下(xià)面的例子中,我們展示了(le)一個步進器(Stepper)組件,它擁有一個标記爲 increment 的可點擊的 DOM 元素。我們還傳入了(le)一個名爲 max 的 prop 防止步進器增長超過 2,因此如果我們點擊了(le)按鈕 3 次,視(shì)圖将仍然顯示 2。

我們不了(le)解這(zhè)個步進器的實現(xiàn)細節,隻知(zhī)道(dào)“輸入”是這(zhè)個 max prop,“輸出”是這(zhè)個組件狀态所呈現(xiàn)出的視(shì)圖。

Vue Test Utils

Cypress

Testing Library

js

const valueSelector = '[data-testid=stepper-value]'

const buttonSelector = '[data-testid=increment]'

const wrapper = mount(Stepper, {

props: {

max: 1

}

})

expect(wrapper.find(valueSelector).text()).toContain('0')

await wrapper.find(buttonSelector).trigger('click')

expect(wrapper.find(valueSelector).text()).toContain('1')

應避免的做法

不要去斷言一個組件實例的私有狀态或測試一個組件的私有方法。測試實現(xiàn)細節會(huì)使測試代碼太脆弱,因爲當實現(xiàn)發生變化時(shí),它們更有可能(néng)失敗并需要更新重寫。

組件的最終工(gōng)作(zuò)是渲染正确的 DOM 輸出,所以專注于 DOM 輸出的測試提供了(le)足夠的正确性保證(如果你(nǐ)不需要更多其他(tā)方面測試的話(huà)),同時(shí)更加健壯、需要的改動更少。

不要完全依賴快(kuài)照測試。斷言 HTML 字符串并不能(néng)完全說明(míng)正确性。應當編寫有意圖的測試。

如果一個方法需要測試,把它提取到(dào)一個獨立的實用(yòng)函數中,并爲它寫一個專門(mén)的單元測試。如果它不能(néng)被直截了(le)當地抽離出來(lái),那麽對(duì)它的調用(yòng)應該作(zuò)爲交互測試的一部分。

推薦方案 ​

Vitest 對(duì)于組件和(hé)組合式函數都采用(yòng)無頭渲染的方式 (例如 VueUse 中的 useFavicon 函數)。組件和(hé) DOM 都可以通過 @testing-library/vue 來(lái)測試。

Cypress 組件測試 會(huì)預期其準确地渲染樣式或者觸發原生 DOM 事(shì)件。可以搭配 @testing-library/cypress 這(zhè)個庫一同進行測試。

Vitest 和(hé)基于浏覽器的運行器之間的主要區(qū)别是速度和(hé)執行上(shàng)下(xià)文(wén)。簡而言之,基于浏覽器的運行器,如 Cypress,可以捕捉到(dào)基于 Node 的運行器(如 Vitest)所不能(néng)捕捉的問題(比如樣式問題、原生 DOM 事(shì)件、Cookies、本地存儲和(hé)網絡故障),但(dàn)基于浏覽器的運行器比 Vitest 慢幾個數量級,因爲它們要執行打開(kāi)浏覽器,編譯樣式表以及其他(tā)步驟。Cypress 是一個基于浏覽器的運行器,支持組件測試。請(qǐng)閱讀 Vitest 文(wén)檔的“比較”這(zhè)一章 了(le)解 Vitest 和(hé) Cypress 最新的比較信息。

組件挂載庫 ​

組件測試通常涉及到(dào)單獨挂載被測試的組件,觸發模拟的用(yòng)戶輸入事(shì)件,并對(duì)渲染的 DOM 輸出進行斷言。有一些(xiē)專門(mén)的工(gōng)具庫可以使這(zhè)些(xiē)任務變得更簡單。

@testing-library/vue 是一個 Vue 的測試庫,專注于測試組件而不依賴其他(tā)實現(xiàn)細節。因其良好(hǎo)的設計(jì)使得代碼重構也(yě)變得非常容易。它的指導原則是,測試代碼越接近軟件的使用(yòng)方式,它們就越值得信賴。

@vue/test-utils 是官方的底層組件測試庫,用(yòng)來(lái)提供給用(yòng)戶訪問 Vue 特有的 API。@testing-library/vue 也(yě)是基于此庫構建的。

我們推薦使用(yòng) @testing-library/vue 測試應用(yòng)中的組件, 因爲它更匹配整個應用(yòng)的測試優先級。隻有在你(nǐ)構建高(gāo)級組件、并需要測試内部的 Vue 特有 API 時(shí)再使用(yòng) @vue/test-utils。

其他(tā)選擇 ​

Nightwatch 是一個端到(dào)端測試運行器,支持 Vue 的組件測試。(Nightwatch v2 版本的 示例項目)

端到(dào)端(E2E)測試 ​

雖然單元測試爲所寫的代碼提供了(le)一定程度的驗證,但(dàn)單元測試和(hé)組件測試在部署到(dào)生産時(shí),對(duì)應用(yòng)整體覆蓋的能(néng)力有限。因此,端到(dào)端測試針對(duì)的可以說是應用(yòng)最重要的方面:當用(yòng)戶實際使用(yòng)你(nǐ)的應用(yòng)時(shí)發生了(le)什(shén)麽。

端到(dào)端測試的重點是多頁面的應用(yòng)表現(xiàn),針對(duì)你(nǐ)的應用(yòng)在生産環境下(xià)進行網絡請(qǐng)求。他(tā)們通常需要建立一個數據庫或其他(tā)形式的後端,甚至可能(néng)針對(duì)一個預備上(shàng)線的環境運行。

端到(dào)端測試通常會(huì)捕捉到(dào)路由、狀态管理(lǐ)庫、頂級組件(常見爲 App 或 Layout)、公共資源或任何請(qǐng)求處理(lǐ)方面的問題。如上(shàng)所述,它們可以捕捉到(dào)單元測試或組件測試無法捕捉的關鍵問題。

端到(dào)端測試不導入任何 Vue 應用(yòng)的代碼,而是完全依靠在真實浏覽器中浏覽整個頁面來(lái)測試你(nǐ)的應用(yòng)。

端到(dào)端測試驗證了(le)你(nǐ)的應用(yòng)中的許多層。可以在你(nǐ)的本地構建的應用(yòng)中,甚至是一個預上(shàng)線的環境中運行。針對(duì)預上(shàng)線環境的測試不僅包括你(nǐ)的前端代碼和(hé)靜态服務器,還包括所有相關的後端服務和(hé)基礎設施。

你(nǐ)的測試越是類似于你(nǐ)的軟件的使用(yòng)方式,它們就越能(néng)值得你(nǐ)信賴。- Kent C. Dodds - Testing Library 的作(zuò)者

通過測試用(yòng)戶操作(zuò)如何影響你(nǐ)的應用(yòng),端到(dào)端測試通常是提高(gāo)應用(yòng)能(néng)否正常運行的置信度的關鍵。

選擇一個端到(dào)端測試解決方案 ​

雖然因爲不可靠且拖慢了(le)開(kāi)發過程,市面上(shàng)對(duì) Web 上(shàng)的端到(dào)端測試的評價并不好(hǎo),但(dàn)現(xiàn)代端到(dào)端工(gōng)具已經在創建更可靠、更有用(yòng)和(hé)交互性更好(hǎo)的測試方面取得了(le)很(hěn)大(dà)進步。在選擇端到(dào)端測試框架時(shí),以下(xià)小(xiǎo)節會(huì)爲你(nǐ)給應用(yòng)選擇測試框架時(shí)需要注意的事(shì)項提供一些(xiē)指導。

跨浏覽器測試 ​

端到(dào)端測試的一個主要優點是你(nǐ)可以了(le)解你(nǐ)的應用(yòng)在多個不同浏覽器上(shàng)運行的情況。盡管理(lǐ)想情況應該是 100% 的跨浏覽器覆蓋率,但(dàn)很(hěn)重要的一點是跨浏覽器測試對(duì)團隊資源的回報(bào)是遞減的,因爲需要額外(wài)的時(shí)間和(hé)機器來(lái)持續運行它們。因此,在選擇應用(yòng)所需的跨浏覽器測試的數量時(shí),注意權衡是很(hěn)有必要的。

更快(kuài)的反饋 ​

端到(dào)端測試和(hé)相應開(kāi)發過程的主要問題之一是,運行整個套件需要很(hěn)長的時(shí)間。通常情況下(xià),這(zhè)隻在持續集成和(hé)部署(CI/CD)管道(dào)中進行。現(xiàn)代的端到(dào)端測試框架通過增加并行化等功能(néng)來(lái)幫助解決這(zhè)個問題,這(zhè)使得 CI/CD 管道(dào)的運行速度比以前快(kuài)了(le)幾倍。此外(wài),在本地開(kāi)發時(shí),能(néng)夠有選擇地爲你(nǐ)正在工(gōng)作(zuò)的頁面運行單個測試,同時(shí)還提供測試的熱重載,大(dà)大(dà)提高(gāo)了(le)開(kāi)發者的工(gōng)作(zuò)流程和(hé)生産力。

第一優先級的調試體驗 ​

傳統上(shàng),開(kāi)發者依靠掃描終端窗口中的日志來(lái)幫助确定測試中出現(xiàn)的問題,而現(xiàn)代端到(dào)端測試框架允許開(kāi)發者利用(yòng)他(tā)們已經熟悉的工(gōng)具,例如浏覽器開(kāi)發工(gōng)具。

無頭模式下(xià)的可見性 ​

當端到(dào)端測試在 CI/CD 管道(dào)中運行時(shí),它們通常在無頭浏覽器(即不帶界面的浏覽器)中運行。因此,當錯誤發生時(shí),現(xiàn)代端到(dào)端測試框架的一個關鍵特性是能(néng)夠在不同的測試階段查看(kàn)應用(yòng)的快(kuài)照、視(shì)頻,從(cóng)而深入了(le)解錯誤的原因。而在很(hěn)早以前,要手動維護這(zhè)些(xiē)集成是非常繁瑣的。

推薦方案 ​

Cypress

總的來(lái)說,我們認爲 Cypress 提供了(le)最完整的端到(dào)端解決方案,其具有信息豐富的圖形界面、出色的調試性、内置斷言和(hé)存根、抗剝落性、并行化和(hé)快(kuài)照等諸多特性。而且如上(shàng)所述,它還提供對(duì) 組件測試 的支持。不過,它隻支持測試基于 Chromium 的浏覽器和(hé) Firefox。

其他(tā)選項 ​

Playwright 也(yě)是一個非常好(hǎo)的端到(dào)端測試解決方案,支持測試範圍更廣的浏覽器品類(主要是 WebKit 型的)。查看(kàn)這(zhè)篇文(wén)章 《爲什(shén)麽選擇 Playwright》 了(le)解更多細節。

Nightwatch v2 是一個基于 Selenium WebDriver 的端到(dào)端測試解決方案。它的浏覽器品類支持範圍是最廣的。

用(yòng)例指南 ​

添加 Vitest 到(dào)項目中 ​

在一個基于 Vite 的 Vue 項目中,運行如下(xià)命令:

sh

> npm install -D vitest happy-dom @testing-library/vue

接着,更新你(nǐ)的 Vite 配置,添加上(shàng) test 選項:

js

// vite.config.js

import { defineConfig } from 'vite'

export default defineConfig({

// ...

test: {

// 啓用(yòng)類似 jest 的全局測試 API

globals: true,

// 使用(yòng) happy-dom 模拟 DOM

// 這(zhè)需要你(nǐ)安裝 happy-dom 作(zuò)爲對(duì)等依賴(peer dependency)

environment: 'happy-dom'

}

})

TIP

如果你(nǐ)在使用(yòng) TypeScript,請(qǐng)将 vitest/globals 添加到(dào) tsconfig.json 的 types 字段當中。

json

// tsconfig.json

{

"compilerOptions": {

"types": ["vitest/globals"]

}

}

接着在你(nǐ)的項目中創建名字以 *.test.js 結尾的文(wén)件。你(nǐ)可以把所有的測試文(wén)件放(fàng)在項目根目錄下(xià)的 test 目錄中,或者放(fàng)在源文(wén)件旁邊的 test 目錄中。Vitest 會(huì)使用(yòng)命名規則自(zì)動搜索它們。

js

// MyComponent.test.js

import { render } from '@testing-library/vue'

import MyComponent from './MyComponent.vue'

test('it should work', () => {

const { getByText } = render(MyComponent, {

props: {

/* ... */

}

})

// 斷言輸出

getByText('...')

})

最後,在 package.json 之中添加測試命令,然後運行它:

json

{

// ...

"scripts": {

"test": "vitest"

}

}

sh

> npm test

測試組合式函數 ​

這(zhè)一小(xiǎo)節假設你(nǐ)已經讀過了(le)組合式函數這(zhè)一章。

當涉及到(dào)測試組合式函數時(shí),我們可以根據是否依賴宿主組件實例把它們分爲兩類。

當一個組合式函數使用(yòng)以下(xià) API 時(shí),它依賴于一個宿主組件實例:

生命周期鈎子

供給/注入

如果一個組合式程序隻使用(yòng)響應式 API,那麽它可以通過直接調用(yòng)并斷言其返回的狀态或方法來(lái)進行測試。

js

// counter.js

import { ref } from 'vue'

export function useCounter() {

const count = ref(0)

const increment = () => count.value++

return {

count,

increment

}

}

js

// counter.test.js

import { useCounter } from './counter.js'

test('useCounter', () => {

const { count, increment } = useCounter()

expect(count.value).toBe(0)

increment()

expect(count.value).toBe(1)

})

一個依賴生命周期鈎子或供給/注入的組合式函數需要被包裝在一個宿主組件中才可以測試。我們可以創建下(xià)面這(zhè)樣的幫手函數:

js

// test-utils.js

import { createApp } from 'vue'

export function withSetup(composable) {

let result

const app = createApp({

setup() {

result = composable()

// 忽略模闆警告

return () => {}

}

})

app.mount(document.createElement('div'))

// 返回結果與應用(yòng)實例

// 用(yòng)來(lái)測試供給和(hé)組件卸載

return [result, app]

}

js

import { withSetup } from './test-utils'

import { useFoo } from './foo'

test('useFoo', () => {

const [result, app] = withSetup(() => useFoo(123))

// 爲注入的測試模拟一方供給

app.provide(...)

// 執行斷言

expect(result.foo.value).toBe(1)

// 如果需要的話(huà)可以這(zhè)樣觸發

app.unmount()

})

對(duì)于更複雜(zá)的組合式函數,通過使用(yòng)組件測試編寫針對(duì)這(zhè)個包裝器組件的測試,這(zhè)會(huì)容易很(hěn)多。

網站(zhàn)建設開(kāi)發|APP設計(jì)開(kāi)發|小(xiǎo)程序建設開(kāi)發
下(xià)一篇:服務端渲染 (SSR)
上(shàng)一篇:狀态管理(lǐ)