
視覺化測試:UI 開發中最厲害的技巧
以更少的維護獲得更多信心

在 UI 開發中,確保所有東西看起來正確與確保其運作同樣重要。視覺化測試是影像快照測試,旨在解決這個問題。
然而,有點令人驚訝的是,它們也可以取代許多 UI 單元測試中最脆弱的部分:斷言 UI 的細節。在許多情況下,這可以完全取代單元測試,讓您能夠以更少的程式碼測試更多內容。
這篇文章涵蓋了
如果您仍然在對元件進行單元測試,請繼續閱讀以了解更好的 UI 開發方式。

視覺化測試 101
在我們深入探討為什麼視覺化測試如此出色之前,它是什麼以及它是如何運作的?
視覺化測試是一種快照測試,它比較程式碼變更之前和之後的 UI 元件影像快照。如果快照不符,則測試失敗。
- 差異可能是預期的,因此必須更新基準(之前)影像
- 或者差異是意外的,使用者應該去修復程式碼。
以下是該流程在實務中的樣子

更少的程式碼,更好的測試
視覺化測試非常棒,但為什麼我們認為它是測試 UI 的一種根本上更好的方式?簡短的答案是,視覺化測試比單元測試更容易編寫和維護。同時,它們提供更多信心,因為它們測試更多內容。
考慮一個使用 React Testing Library (RTL) 的簡單範例,這是像 Jest 和 Vitest 這樣的測試執行器中最流行的單元測試元件方式。
// Button.test.js
import { render, screen } from '@testing-library/react';
import Button from './Button';
it('uses custom text for the button label', () => {
render(<Button>Click me!</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me!');
})
此測試掛載了 Button
元件,然後斷言按鈕標籤的文字內容。像 Playwright CT 和 Cypress CT 這樣的工具也使用類似的語法和結構。
Storybook 的語法略有不同,但概念相同。以下是 RTL 範例的等效範例
// Button.stories.js
import Button from './Button';
export default { component: Button };
export const CustomText = {
args: { children: 'Click me!' },
play: async ({ canvasElement }) => {
await expect(canvasElement).toHaveTextContent('Click me!')
},
};
這是在 Storybook 內部的樣子

透過像這樣的測試,我們僅斷言一件事:按鈕的文字。
視覺化測試不僅斷言按鈕包含正確的文字,還斷言按鈕是藍色的、具有圓角、以相同的字體呈現等等。而且它們在沒有編寫任何明確斷言的情況下做到這一點。
以下是使用 Visual Tests 擴充套件後,測試變得多麼簡單
export const CustomText = {
args: { children: 'Click me!' },
};
在以下範例中,我不小心在全球 CSS 中引入了一個錯誤,該錯誤剝奪了 Button 的大部分樣式。這將通過 RTL 的功能測試,但我們的視覺化測試會捕捉到差異並將其顯示為變更

真實世界範例
節省一行斷言似乎沒什麼大不了的,但在真實世界的專案中,好處會迅速累積。考慮像 Mealdrop 的購物車這樣的元件

在功能上,我們想要測試購物車中的所有商品是否正確顯示,以及結帳按鈕是否已啟用,因為購物車中有商品。
透過視覺化測試,我們可以使用 story WithItems
來測試這一點,該 story 設定了購物車及其輸入,但實際上不包含任何明確的測試邏輯
// ShoppingCartMenu.stories.js
import { ShoppingCartMenu } from './ShoppingCartMenu'
export default { component: ShoppingCartMenu };
export const WithItems = {
args: {
cartItems: [ /* items */ ],
totalPrice: 1200
},
}
如果我們不相信已啟用的按鈕會在 UI 中呈現不同的樣子,我們可以擴充該測試以定義 WithItemsEnabled
,該測試專門驗證按鈕未停用
// ShoppingCartMenu.stories.js
export const WithItemsEnabled = {
...WithItems,
play: async ({ canvasElement }) => {
const checkout = await findByRole(canvasElement, 'button');
await expect(checkout).not.toBeDisabled();
},
}
現在想像一下僅在 RTL 中編寫相同的測試。我們會想要測試購物車中的每個商品是否以正確的數量出現,總計是否正確等等。
// ShoppingCartMenu.test.js
it('renders correctly with items', () => {
render(<ShoppingCartMenu cartItems={[ /* items */ ]} totalPrice={1200} />);
const fries = await screen.findByText(/^Fries$/);
expect(getByText(fries.parentElement, '€2.50')).toBeInTheDocument();
// More assertions here
const cheeseburger = await screen.findByText(/^Cheeseburger$/);
expect(getByText(cheeseburger.parentElement, '€8.50')).toBeInTheDocument();
// More assertions here
/*
*
*
* Dozens of lines omitted here,
* for everybody's sanity.
*
*
*/
const checkout = screen.getByRole('button');
expect(checkout).not.toBeDisabled();
});
當然,我們可以透過輔助函數來縮短所有這些內容,以檢查每個購物車商品,但是當我們需要為像這樣的測試編寫和維護輔助函數時,我們已經失敗了。
現在將這個單一測試乘以整個應用程式,其中可能包含數百個各種複雜性的元件。維護這類測試是一場噩夢。
相反,為數百個元件編寫 story 並對其進行視覺化測試是可行的,而且世界上最好的前端團隊已經在這樣做了。
測試 UX,而不是實作細節
測試大師 Cory House 最近評論了某人關於「自動化測試就像在程式碼上澆灌混凝土」的觀點。上一節中的 RTL 程式碼正是人們在自動化測試中抱怨的「混凝土」。
「自動化測試就像在程式碼上澆灌混凝土。」
— Cory House (@housecor) 2024 年 5 月 26 日
我不同意。
程式碼是可塑的。混凝土不是。
為了避免測試感覺像「在程式碼上澆灌混凝土」
1. 在需求明確之前不要編碼。
2. 避免要求 100% 程式碼覆蓋率。
3. 測試 UX,而不是…
為了避免混凝土,Cory 建議「測試 UX,而不是實作細節」。而測試 UX 正是視覺化測試給我們帶來的。更重要的是,視覺化快照比程式碼更容易維護:正如我們在上面看到的,當您的 story 以所需狀態呈現時,更新測試就像按下按鈕接受新的基準快照一樣容易。
由於 Storybook 也支援 RTL 動作和查詢,因此您擁有盡可能多的能力來測試所需的任何細節層級,以獲得對程式碼的信心。
Storybook 中的視覺化測試
在 Storybook,我們非常堅信視覺化測試,因此我們已將其作為一項一流功能包含在內。Storybook 的 Visual Test 擴充套件由Chromatic提供支援,Chromatic 是世界上最好的視覺化測試基礎設施。
Chromatic 透過比較程式碼更新前後的影像快照來識別變更,並突出顯示差異以供審查。它在雲端中以數十秒的速度並行執行數千個測試,跨越多個瀏覽器(Chrome、Safari、Firefox、Edge)、視窗大小、主題和 i18n 地區設定。

Chromatic 提供 PR 檢查,以指示何時存在與 PR 關聯的視覺變更。當測試失敗時,使用者可以點擊進入高效的 UI 以查看視覺變更。到目前為止,PR 檢查一直是使用 Chromatic 和其他類似視覺化測試服務的主要工作流程。
Storybook 的Visual Tests 擴充套件是此工作流程的一種全新且創新的轉變,將 Chromatic 的強大功能置於 Storybook 本身內部。這讓您可以在開發時按需執行視覺化測試,而無需推送程式碼、執行 CI 並等待一堆不相關的檢查。
這是一個很棒的工作流程。從您的元件工作坊中,現在可以
- 啟動視覺化測試
- 篩選側邊欄以突出顯示視覺差異
- 在 Storybook 內部查看並解決這些變更

Visual Tests 擴充套件使捕捉 UI 錯誤並在建構元件時保持流程比以往任何時候都更快。我們相信這是邁向 UI 開發「聖杯」的重要一步。
立即試用
Storybook 的 Visual Test 擴充套件包含在新的 Storybook 安裝中
npx storybook@latest init
如果您是從較舊版本的 Storybook 升級,您現在將被提示選擇是否要將擴充套件安裝到現有專案中
npx storybook@latest upgrade
下一步是什麼?
Visual Tests 擴充套件在今天的 Storybook 8 中是穩定且可用的。我們正在考慮以下增強功能
- 全螢幕檢視模式以接受和拒絕變更。
- 將測試範圍限定為目前可見的 story 或元件的能力。
- 始終開啟的「監看模式」,在您的開發機器上本地執行功能測試,並透過更快的意見回饋迴圈來補充視覺化測試。
如需我們正在考慮和積極進行的專案概述,請查看Storybook 的路線圖。
我們真的相信視覺化測試和我們新的 Visual Tests 擴充套件是測試 UI 的最佳方式,這讓您在開發時充滿信心。
— Storybook (@storybookjs) 2024 年 6 月 20 日
我們寫了一篇文章來解釋原因。https://#/p6IbzHh1B0