做(zuò)自(zì)由與創造的先行(xíng)者

測試

Vue.js中文手冊

為(wèi)什麽需要(yào)測試 ​

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

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

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

何時(shí)測試 ​

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

測試的類型 ​

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

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

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

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

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

總覽 ​

我們将簡要(yào)地(dì)讨論這(zhè)些測試是什麽,以及如(rú)何在 Vue 應用中實現它們,并提供一(yī)些普适性建議(yì)。

單元測試 ​

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

一(yī)般來說,單元測試将捕獲函數(shù)的業務邏輯和(hé)邏輯正确性的問(wèn)題。

以這(zhè)個 increment 函數(shù)為(wèi)例:

js

// helpers.js

export function increment (current, max = 10) {

if (current < max) {

return current + 1

}

return current

}

因為(wèi)它很(hěn)獨立,可(kě)以很(hěn)容易地(dì)調用 increment 函數(shù)并斷言它是否返回了(le)所期望的內(nèi)容,所以我們将編寫一(yī)個單元測試。

如(rú)果任何一(yī)條斷言失敗了(le),那(nà)麽問(wèn)題一(yī)定是出在 increment 函數(shù)上(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)

})

})

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

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

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

組合式函數(shù)

組件

組合式函數(shù) ​

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

組件的單元測試 ​

一(yī)個組件可(kě)以通(tōng)過兩種方式測試:

白盒:單元測試

白盒測試知曉一(yī)個組件的實現細節和(hé)依賴關系。它們更專注于将組件進行(xíng)更 獨立 的測試。這(zhè)些測試通(tōng)常會涉及到模拟一(yī)些組件的部分子組件,以及設置插件的狀态和(hé)依賴性(例如(rú) Piana)。

黑(hēi)盒:組件測試

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

推薦方案 ​

Vitest

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

其他(tā)選擇 ​

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

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

組件測試 ​

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

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

組件測試不應該模拟子組件,而應該像用戶一(yī)樣,通(tōng)過與組件互動來測試組件和(hé)其子組件之間(jiān)的交互。例如(rú),組件測試應該像用戶那(nà)樣點擊一(yī)個元素,而不是編程式地(dì)與組件進行(xíng)交互。

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

推薦的做(zuò)法

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

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

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

我們不了(le)解這(zhè)個步進器的實現細節,隻知道(dào)“輸入”是這(zhè)個 max prop,“輸出”是這(zhè)個組件狀态所呈現出的視(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')

應避免的做(zuò)法

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

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

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

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

推薦方案 ​

Vitest 對于組件和(hé)組合式函數(shù)都(dōu)采用無頭渲染的方式 (例如(rú) VueUse 中的 useFavicon 函數(shù))。組件和(hé) DOM 都(dōu)可(kě)以通(tōng)過 @testing-library/vue 來測試。

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

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

組件挂載庫 ​

組件測試通(tōng)常涉及到單獨挂載被測試的組件,觸發模拟的用戶輸入事件,并對渲染的 DOM 輸出進行(xíng)斷言。有一(yī)些專門的工(gōng)具庫可(kě)以使這(zhè)些任務變得更簡單。

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

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

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

其他(tā)選擇 ​

Nightwatch 是一(yī)個端到端測試運行(xíng)器,支持 Vue 的組件測試。(Nightwatch v2 版本的 示例項目)

端到端(E2E)測試 ​

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

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

端到端測試通(tōng)常會捕捉到路由、狀态管理庫、頂級組件(常見(jiàn)為(wèi) App 或 Layout)、公共資源或任何請(qǐng)求處理方面的問(wèn)題。如(rú)上(shàng)所述,它們可(kě)以捕捉到單元測試或組件測試無法捕捉的關鍵問(wèn)題。

端到端測試不導入任何 Vue 應用的代碼,而是完全依靠在真實浏覽器中浏覽整個頁面來測試你的應用。

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

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

通(tōng)過測試用戶操作(zuò)如(rú)何影響你的應用,端到端測試通(tōng)常是提高應用能(néng)否正常運行(xíng)的置信度的關鍵。

選擇一(yī)個端到端測試解決方案 ​

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

跨浏覽器測試 ​

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

更快(kuài)的反饋 ​

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

第一(yī)優先級的調試體驗 ​

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

無頭模式下(xià)的可(kě)見(jiàn)性 ​

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

推薦方案 ​

Cypress

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

其他(tā)選項 ​

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

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

用例指南 ​

添加 Vitest 到項目中 ​

在一(yī)個基于 Vite 的 Vue 項目中,運行(xíng)如(rú)下(xià)命令:

sh

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

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

js

// vite.config.js

import { defineConfig } from 'vite'

export default defineConfig({

// ...

test: {

// 啓用類似 jest 的全局測試 API

globals: true,

// 使用 happy-dom 模拟 DOM

// 這(zhè)需要(yào)你安裝 happy-dom 作(zuò)為(wèi)對等依賴(peer dependency)

environment: 'happy-dom'

}

})

TIP

如(rú)果你在使用 TypeScript,請(qǐng)将 vitest/globals 添加到 tsconfig.json 的 types 字段當中。

json

// tsconfig.json

{

"compilerOptions": {

"types": ["vitest/globals"]

}

}

接着在你的項目中創建名字以 *.test.js 結尾的文件。你可(kě)以把所有的測試文件放在項目根目錄下(xià)的 test 目錄中,或者放在源文件旁邊的 test 目錄中。Vitest 會使用命名規則自(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 之中添加測試命令,然後運行(xíng)它:

json

{

// ...

"scripts": {

"test": "vitest"

}

}

sh

> npm test

測試組合式函數(shù) ​

這(zhè)一(yī)小節假設你已經讀過了(le)組合式函數(shù)這(zhè)一(yī)章(zhāng)。

當涉及到測試組合式函數(shù)時(shí),我們可(kě)以根據是否依賴宿主組件實例把它們分為(wèi)兩類。

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

生命周期鈎子

供給/注入

如(rú)果一(yī)個組合式程序隻使用響應式 API,那(nà)麽它可(kě)以通(tōng)過直接調用并斷言其返回的狀态或方法來進行(xíng)測試。

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)

})

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

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'))

// 返回結果與應用實例

// 用來測試供給和(hé)組件卸載

return [result, app]

}

js

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

import { useFoo } from './foo'

test('useFoo', () => {

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

// 為(wèi)注入的測試模拟一(yī)方供給

app.provide(...)

// 執行(xíng)斷言

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

// 如(rú)果需要(yào)的話可(kě)以這(zhè)樣觸發

app.unmount()

})

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

網站建設開(kāi)發|APP設計開(kāi)發|小程序建設開(kāi)發
下(xià)一(yī)篇:服務端渲染 (SSR)
上(shàng)一(yī)篇:狀态管理