EP49 - 測試

更新於 2024/10/26閱讀時間約 18 分鐘
Testing 每次測試這一塊都是內心想花點時間實作
最後都沒時間認真實作www
測試還是蠻重要的吧 哈

為什麼要測試? Why Test?

自動化測試能幫助您和您的團隊快速且自信地構建複雜的 Vue 應用,透過防止回歸並鼓勵您將應用拆分為可測試的函數、模組、類別和組件。與所有應用一樣,新的 Vue 應用可能會以多種方式出錯,因此在發布前能發現並修復這些問題是很重要的。

在本指南中,我們將介紹基本術語,並提供我們推薦的 Vue 3 應用測試工具。此外,指南包含針對 Vue 特定的可組合項目測試的部分,詳情請參考下方的「測試可組合項目」部分。

何時進行測試? - When to Test

越早開始測試越好!我們建議您儘早編寫測試。拖得越久,應用的相依性就越多,開始測試的難度也會越高。

測試類型 - Testing Types

設計 Vue 應用的測試策略時,您應考慮以下測試類型:

  • 單元測試:檢查給定函數、類別或可組合的輸入是否產生預期的輸出或副作用。
  • 元件測試:檢查元件的掛載、渲染、互動及預期行為。這些測試會導入比單元測試更多的程式碼,較為複雜且執行時間較長。
  • 端對端測試:檢查跨多頁面的功能,並針對生產構建的 Vue 應用進行真實網路請求。這些測試通常需要啟用資料庫或其他後端。

每種測試類型在應用的測試策略中都發揮著作用,並能防範不同類型的問題。

概述 - Overview

我們將簡要討論每種類型的測試是什麼、如何在 Vue 應用中實現它們,並提供一些一般建議。

單元測試 - Unit Test

單元測試用來驗證小而獨立的代碼單元運行是否符合預期。通常測試一個函數、類別、可組合的功能或模組,關注邏輯正確性,只涵蓋應用的一小部分功能,可能模擬應用的環境(如初始狀態、複雜類別、第三方模組和網絡請求)。

一般來說,單元測試可以捕捉到函數業務邏輯和邏輯正確性的問題。

例如此增量函數:

// helpers.js
export function increment (current, max = 10) {
if (current < max) {
return current + 1;
}
return current;
}

這個函數是獨立的,很容易進行測試,確保返回預期結果。

// 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);
});
});

單元測試通常針對不涉及 UI 渲染、網絡請求或環境的業務邏輯。

Vue 應用中有兩個 Vue 特定的單元測試場景:

  1. 可組合功能(Composables)
  2. 組件

可組合功能

Vue 應用中特有的可組合功能可能需要特殊測試處理,詳見「測試可組合功能」。

單元測試組件

組件測試有兩種方式:

  • 白箱測試:關注組件的實現細節和依賴,通常模擬組件的子組件和插件狀態,進行隔離測試。
  • 黑箱測試:不關注組件的實現細節,盡量少模擬子組件,以測試組件和系統的整合。

以下組件測試建議如下

建議使用 Vitest

由於 Vue 官方使用 Vite 建立配置,建議使用 Vitest 進行單元測試。Vitest 由 Vue / Vite 團隊開發,與 Vite 集成簡便且速度快。

其他選擇

Jest 是常見的單元測試框架,但僅當已有 Jest 測試集需遷移至 Vite 時使用,否則建議使用 Vitest 以獲得更佳的效能和整合。

組件測試 - Component Testing​

在 Vue 應用中,組件是 UI 的主要構建塊,因此它們成為驗證應用行為時自然的隔離單元。從粒度上來看,組件測試位於單元測試之上,可視為一種整合測試。大部分 Vue 應用應覆蓋組件測試,建議為每個 Vue 組件編寫專屬的測試文件。

組件測試應捕捉與組件屬性、事件、插槽、樣式、類別、生命週期勾子等相關的問題。組件測試不應模擬子組件,而應透過用戶互動方式測試組件與子組件間的交互。例如,測試中應透過點擊元素來操作組件,而不是直接程式化操作。

組件測試應關注組件的公開介面,而非內部實現細節。對於大多數組件,公開介面僅限於:發出的事件、屬性與插槽。測試時應著重於組件的行為,而非其實現。

應該做的事 - Do

  • 對視覺邏輯:依據輸入的屬性和插槽確認渲染輸出是否正確。
  • 對行為邏輯:驗證對用戶輸入事件的正確渲染更新或發出的事件。

以下範例展示了 Stepper 組件,其包含一個標示為 "increment" 的 DOM 元素,且可以點擊。我們傳遞一個 max 屬性,防止 Stepper 增加到超過 2,所以點擊 3 次後,UI 仍應顯示 2。

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

避免 Don't

  • 不要檢查組件實例的私有狀態或測試組件的私有方法,測試實現細節會使測試脆弱,且更容易因實現變更而需要更新。
  • 不要完全依賴快照測試,僅檢查 HTML 字串不能保證正確性。應撰寫具明確目的的測試。
  • 若需詳細測試方法,建議將其提取為單獨的工具函數並針對其進行單元測試。

推薦工具 - Recommendation

Vitest 與基於瀏覽器的測試執行器之間的主要差異在於速度與執行環境。簡而言之,像 Cypress 這樣的瀏覽器執行器可以發現一些 Node.js 執行器(如 Vitest)無法檢測的問題(例如樣式問題、真實的原生 DOM 事件、Cookies、本地儲存和網絡故障)。然而,瀏覽器執行器的速度比 Vitest 慢很多,因為它需要開啟瀏覽器、編譯樣式等。Cypress 是一款支援組件測試的瀏覽器執行器。請參考 Vitest 的比較頁面以獲取 Vitest 與 Cypress 最新的比較資訊。

掛載庫 - Mounting Libraries

組件測試通常涉及單獨掛載組件、模擬用戶事件並驗證 DOM 輸出,有專門工具庫可簡化這些任務。

  • @vue/test-utils:官方低階組件測試庫,提供 Vue 特定 API。@testing-library/vue 底層基於此庫。
  • @testing-library/vue:專注於不依賴實現細節的組件測試,其原則是測試越接近真實使用,越能提供信心。對於含 Suspense 的異步組件應謹慎使用。

我們建議在應用程式中使用 @vue/test-utils 來測試組件。@testing-library/vue 在測試具有 Suspense 的非同步組件時存在一些問題,因此應謹慎使用。

其他選項

  • Nightwatch:端到端測試工具,支援 Vue 組件測試(範例專案)。
  • WebdriverIO:跨瀏覽器組件測試工具,基於標準化自動化執行原生用戶互動,也可與 Testing Library 搭配使用。

E2E 測試 - E2E Testing​

雖然單元測試能提供一定的信心,但單元和組件測試無法全面涵蓋應用在生產環境中運行時的情況。因此,端到端(E2E)測試提供了更全面的覆蓋範圍,專注於應用實際運行時的行為。

E2E 測試的重點在於多頁面應用的行為,它會針對已部署的 Vue 應用進行網路請求。通常需要連接資料庫或其他後端服務,甚至可能在測試環境中執行。

E2E 測試通常可以檢測到路由、狀態管理、頂層組件(例如 App 或 Layout)、公共資源或請求處理上的問題。它不會匯入任何 Vue 應用程式的程式碼,而是透過瀏覽器模擬實際操作應用的情況。

E2E 測試涵蓋應用的多層面。可以在本地或線上測試環境中測試,這樣不僅涵蓋前端和靜態伺服器,還涵蓋後端服務和基礎設施。

Kent C. Dodds 說道:「測試越貼近真實使用情況,能提供的信心就越高。」

E2E 測試透過模擬使用者行為提升信心,確保應用正常運行。

選擇 E2E 測試解決方案

雖然 E2E 測試曾因不穩定而惡名昭彰,但現代工具已經改善了這一問題。選擇 E2E 測試框架時,請考慮以下幾點。

跨瀏覽器測試

E2E 測試的主要優勢是能跨多個瀏覽器測試應用,但 100% 覆蓋的效益可能隨資源投入而遞減。選擇適合的跨瀏覽器測試程度有助於資源最佳化。

更快速的反饋循環

E2E 測試執行時間長,通常僅在 CI/CD 中完整執行。現代 E2E 框架支援平行執行來提升速度,並支援單獨測試和測試熱重新加載,提升開發效率。

一流的除錯體驗

傳統上需查看終端日誌來排錯,現代框架則允許使用瀏覽器開發工具,提升除錯便利性。

無頭模式的可視化

在 CI/CD 管線中,E2E 測試常使用無頭瀏覽器執行。現代 E2E 框架提供截圖或錄影功能,有助於識別錯誤原因。

推薦工具

Playwright:支援 Chromium、WebKit 和 Firefox,能在 Windows、Linux 和 macOS 執行。擁有出色的 UI、除錯功能、內建斷言、平行執行和防止不穩定測試的設計。支援組件測試(實驗性)。

Cypress:具備豐富的圖形介面、出色的除錯功能、內建斷言、快照等。支援 Chromium、Firefox 和 Electron。WebKit 支援為實驗性,部分平行化功能需 Cypress Cloud 訂閱。

其他選擇

Nightwatch:基於 Selenium WebDriver,支援最多瀏覽器。

WebdriverIO:基於 WebDriver 協議,適用於網頁和行動裝置的測試框架。

範例 - Recipes

將 Vitest 添加到專案中

在基於 Vite 的 Vue 專案中,運行以下命令:

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

接下來,更新 Vite 配置以添加測試選項區塊:

// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
// ...
test: {
// 啟用類似 Jest 的全域測試 API
globals: true,
// 使用 happy-dom 模擬 DOM
// (需要將 happy-dom 安裝為 peer 依賴)
environment: 'happy-dom'
}
})
提示:如果您使用 TypeScript,請在 tsconfig.jsontypes 欄位中添加 vitest/globals
// tsconfig.json
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}

然後,在您的專案中創建一個以 .test.js 結尾的檔案。您可以將所有測試檔案放在專案根目錄的 test 目錄中,或放在與源檔案相鄰的 test 目錄中。Vitest 將自動使用命名慣例搜尋它們。

// 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 以添加測試腳本並運行它:

{
// ...
"scripts": {
"test": "vitest"
}
}
> npm test

測試可組合函式

本節假設您已閱讀過可組合函式的相關內容。

在測試可組合函式時,可以將其分為兩類:不依賴於宿主組件實例的可組合函式,以及依賴於宿主組件實例的可組合函式。

當可組合函式使用以下 API 時,則依賴於宿主組件實例:

  • 生命週期鉤子
  • 提供 / 注入

如果可組合函式僅使用響應式 API,那麼可以直接調用它並驗證其返回的狀態/方法:

// counter.js
import { ref } from 'vue'

export function useCounter() {
const count = ref(0)
const increment = () => count.value++

return {
count,
increment
}
}
// 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)
})

依賴於生命週期鉤子或提供 / 注入的可組合函式需要包裹在宿主組件中進行測試。我們可以創建一個幫助函式,如下所示:

// 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'))
// 返回結果和應用實例
// 以便測試提供/卸載
return [result, app]
}
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)
// 如果需要,觸發 onUnmounted 鉤子
app.unmount()
})

對於更複雜的可組合函式,通過使用組件測試技術編寫測試包裝組件也可能更容易進行測試。

測試的章節比我想像中的還多資訊@@
真的要實作過會比較有印象....Orz...
繼續堆疊知識中...www
avatar-img
2會員
71內容數
分享生活趣事~
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
卡關的人生 的其他內容
狀態管理在 Vue 應用中非常重要,尤其是當多個組件需要共享狀態時。每個 Vue 組件管理自己的響應式狀態,但隨著組件數量增加,簡單的管理方式可能變得複雜。這時,可以使用如 Pinia 的狀態管理庫來簡化這一過程。Pinia 提供了一個更簡單的 API 和更強的類型推斷,並由 Vue 核心團隊維護。
前後端路由的協作是現代應用開發的重要部分。後端路由根據用戶的 URL 發送相應的回應,而前端路由則使得單頁應用(SPA)能在不重新加載整個頁面的情況下更新內容。前端路由會攔截用戶的導航,並動態加載所需的組件。
Vite 作為輕量快速的建置工具,原生支持 Vue SFC,並且提供簡化的配置與優越的開發體驗。文章還介紹了 IDE 支援、測試工具(如 Cypress 和 Vitest)、Linting 和格式化工具的使用,幫助開發者提高開發效率與代碼質量。
Vue 單文件元件(SFC)是 *.vue 文件,將模板、邏輯和樣式封裝在一個文件中,提供模組化開發。SFC 需建置步驟,但帶來多項好處,如元件範圍內的 CSS、預編譯模板和熱模塊替換(HMR)。適合單頁應用、靜態網站生成等前端專案。
<Suspense> 是 Vue 3 的一個實驗性組件,用於協調異步組件的加載狀態,簡化異步處理。它能在等待多個嵌套組件的異步依賴解決時顯示加載指示器,避免每個組件獨立處理加載和錯誤狀態,從而提高用戶體驗。
<Teleport> 是 Vue.js 的內建組件,允許我們將組件的部分內容「傳送」到 DOM 的不同位置,即使這些內容邏輯上屬於某個組件,但可以在視覺上顯示在其他位置,常用於模態框和覆蓋層等情境。
狀態管理在 Vue 應用中非常重要,尤其是當多個組件需要共享狀態時。每個 Vue 組件管理自己的響應式狀態,但隨著組件數量增加,簡單的管理方式可能變得複雜。這時,可以使用如 Pinia 的狀態管理庫來簡化這一過程。Pinia 提供了一個更簡單的 API 和更強的類型推斷,並由 Vue 核心團隊維護。
前後端路由的協作是現代應用開發的重要部分。後端路由根據用戶的 URL 發送相應的回應,而前端路由則使得單頁應用(SPA)能在不重新加載整個頁面的情況下更新內容。前端路由會攔截用戶的導航,並動態加載所需的組件。
Vite 作為輕量快速的建置工具,原生支持 Vue SFC,並且提供簡化的配置與優越的開發體驗。文章還介紹了 IDE 支援、測試工具(如 Cypress 和 Vitest)、Linting 和格式化工具的使用,幫助開發者提高開發效率與代碼質量。
Vue 單文件元件(SFC)是 *.vue 文件,將模板、邏輯和樣式封裝在一個文件中,提供模組化開發。SFC 需建置步驟,但帶來多項好處,如元件範圍內的 CSS、預編譯模板和熱模塊替換(HMR)。適合單頁應用、靜態網站生成等前端專案。
<Suspense> 是 Vue 3 的一個實驗性組件,用於協調異步組件的加載狀態,簡化異步處理。它能在等待多個嵌套組件的異步依賴解決時顯示加載指示器,避免每個組件獨立處理加載和錯誤狀態,從而提高用戶體驗。
<Teleport> 是 Vue.js 的內建組件,允許我們將組件的部分內容「傳送」到 DOM 的不同位置,即使這些內容邏輯上屬於某個組件,但可以在視覺上顯示在其他位置,常用於模態框和覆蓋層等情境。
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
當我們架好站、WebService測試完,接著就是測試區域網路連線啦~
Thumbnail
你好,在下最近在學習開發web,學了html css js,也得出一些心得,由於網路上已有許多教學,所以我會著重在如何開發出to do List,以及解釋我寫的程式碼。相關的教學我會直接貼網址。如果我有什麼地方出錯,或者是可以寫得更好,歡迎在下方留言,討論。 首先先介紹我的開發環境: 我用了vs
Thumbnail
理解一個全新的操作環境有時候可能是一個挑戰,尤其對於那些剛開始接觸VS Code的開發者來說,即便具備一定的英文閱讀能力,可能也會對這個陌生的操作環境感到徬徨和不安。不過,沒有必要擔心,我們接下來就來一起用短短的一分鐘時間,將VS Code轉換成最熟悉的中文環境吧! 安裝繁體中文語言包 由於VS
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
※ TypeScript範例說明: interface ITest { test1: string test2: number print: (arg: string[]) => boolean } class Test implements ITest { public te
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
當我們架好站、WebService測試完,接著就是測試區域網路連線啦~
Thumbnail
你好,在下最近在學習開發web,學了html css js,也得出一些心得,由於網路上已有許多教學,所以我會著重在如何開發出to do List,以及解釋我寫的程式碼。相關的教學我會直接貼網址。如果我有什麼地方出錯,或者是可以寫得更好,歡迎在下方留言,討論。 首先先介紹我的開發環境: 我用了vs
Thumbnail
理解一個全新的操作環境有時候可能是一個挑戰,尤其對於那些剛開始接觸VS Code的開發者來說,即便具備一定的英文閱讀能力,可能也會對這個陌生的操作環境感到徬徨和不安。不過,沒有必要擔心,我們接下來就來一起用短短的一分鐘時間,將VS Code轉換成最熟悉的中文環境吧! 安裝繁體中文語言包 由於VS
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
※ TypeScript範例說明: interface ITest { test1: string test2: number print: (arg: string[]) => boolean } class Test implements ITest { public te