
複合組件測試
防止小變動演變成重大錯誤

特斯拉剛召回 158,000 輛車,因為一個模組(顯示器)故障。顯示器控制台故障時,您將無法使用倒車鏡頭、方向燈或駕駛輔助系統。這會顯著增加撞車風險。
一個有缺陷的模組升級為重大故障。
UI 也面臨類似的挑戰,因為應用程式就像汽車一樣,是零件相互連接的網路。一個組件中的錯誤會影響周圍的所有其他組件。更不用說應用程式中每個使用到它的部分。測試 UI 組件的組成方式有助於您預防此類錯誤。
在前一篇文章中,我們學習了如何使用 Storybook 獨立建構組件、編寫視覺化測試,以及使用 Chromatic 自動捕捉迴歸錯誤。然而,測試 UI 中更複雜的部分是很棘手的。它們是由許多更簡單的組件組合而成,並且也連接到應用程式狀態。
本文教您如何隔離複合組件並對其應用視覺化測試。在此過程中,您將學習有關模擬資料和模擬應用程式邏輯的知識。以及測試組件整合的方法。
小錯誤最終會破壞應用程式
應用程式是透過將組件插入彼此來建構的。這表示一個元素中的錯誤可能會影響其鄰近元素。例如,重新命名 prop 可能會中斷從父組件到子組件的資料流。或者,UI 元素中不正確的 CSS 通常會導致版面配置損壞。


考慮Storybook 設計系統中的 Button 組件。它在多個頁面中被無數次使用。Button 中的錯誤將會不經意地導致所有這些頁面中出現錯誤。換句話說,一個故障可能會呈指數級增長。當您向上移動組件層次結構朝向頁面層級時,這些錯誤的影響會增加。因此,我們需要一種方法來及早發現此類級聯問題並找出根本原因。

組合測試
視覺化測試透過捕獲和比較故事的影像快照(在真實瀏覽器中)來捕捉錯誤。這使其成為發現 UI 變更和識別根本原因的理想選擇。以下是流程的快速提醒
- 🏷 隔離組件。使用 Storybook 一次測試一個組件。
- ✍🏽 寫出測試案例。每個組件狀態都使用 props 重現。
- 🔍 手動驗證每個測試案例的外觀。
- 📸 使用視覺迴歸測試自動捕捉錯誤。
組合測試完全是在由多個更簡單的組件組成的樹狀結構中較高層級的「複合」組件上執行視覺化測試。這樣,您就可以量化任何變更可能對整個應用程式產生的影響。並確保系統整體運作正常。
關鍵差異在於,複合組件追蹤應用程式狀態並將行為向下傳遞到樹狀結構中。在編寫測試案例時,您必須考慮到這些。
讓我們看看實際操作過程。我們將使用我在第 2 部分中介紹的 Taskbox 應用程式。取得程式碼並跟著操作。我們的起點是 visual-testing
分支。
教學
TaskList
顯示使用者所屬的完整任務列表。它將釘選的任務移到列表頂部。並具有載入和空白狀態。我們將從為所有這些情境編寫故事開始。

建立故事檔案,註冊 TaskList
組件,並為預設案例新增一個故事。
// TaskList.stories.js
import React from 'react';
import { TaskList } from './TaskList';
import Task from './Task.stories';
export default {
component: TaskList,
title: 'TaskList',
argTypes: {
...Task.argTypes,
},
};
const Template = (args) => <TaskList {...args} />;
export const Default = Template.bind({});
Default.args = {
tasks: [
{ id: '1', state: 'TASK_INBOX', title: 'Build a date picker' },
{ id: '2', state: 'TASK_INBOX', title: 'QA dropdown' },
{
id: '3',
state: 'TASK_INBOX',
title: 'Write a schema for account avatar component',
},
{ id: '4', state: 'TASK_INBOX', title: 'Export logo' },
{ id: '5', state: 'TASK_INBOX', title: 'Fix bug in input error state' },
{ id: '6', state: 'TASK_INBOX', title: 'Draft monthly blog to customers' },
],
};
請注意 argTypes
。Args 是 Storybook 用於定義故事輸入的機制。將它們視為與框架無關的 props。在組件層級定義的 Args 會自動向下傳遞到每個故事。在我們的案例中,我們使用 Actions 擴充套件定義了三個事件處理程序。
當您與 TaskList
互動時,這些模擬動作將顯示在擴充套件面板中。讓您可以驗證組件是否正確連接。

組合 Args
就像您組合組件來建立新的 UI 一樣,您可以組合 Args 來建立新的故事。複合組件的 Args 通常甚至會組合其子組件的 Args。
事件處理程序 Args 已在 Task 故事檔案中定義,我們可以重複使用。同樣地,我們也可以使用預設故事中的 Args 來建立釘選任務故事。
// TaskList.stories.js
import React from 'react';
import { TaskList } from './TaskList';
import Task from './Task.stories';
export default {
component: TaskList,
title: 'TaskList',
argTypes: {
...Task.argTypes,
},
};
const Template = (args) => <TaskList {...args} />;
export const Default = Template.bind({});
Default.args = {
tasks: [
{ id: '1', state: 'TASK_INBOX', title: 'Build a date picker' },
{ id: '2', state: 'TASK_INBOX', title: 'QA dropdown' },
{
id: '3',
state: 'TASK_INBOX',
title: 'Write a schema for account avatar component',
},
{ id: '4', state: 'TASK_INBOX', title: 'Export logo' },
{ id: '5', state: 'TASK_INBOX', title: 'Fix bug in input error state' },
{ id: '6', state: 'TASK_INBOX', title: 'Draft monthly blog to customers' },
],
};
export const WithPinnedTasks = Template.bind({});
WithPinnedTasks.args = {
tasks: [
{ id: '6', title: 'Draft monthly blog to customers', state: 'TASK_PINNED' },
...Default.args.tasks.slice(0, 5),
],
};
export const Loading = Template.bind({});
Loading.args = {
tasks: [],
loading: true,
};
export const Empty = Template.bind({});
Empty.args = {
...Loading.args,
loading: false,
};
透過 Args 組合來塑造故事是一種強大的技術。它使我們能夠編寫故事,而無需一遍又一遍地重複相同的 props。更重要的是,它可以測試組件整合。如果您重新命名其中一個 Task
組件 prop,那將導致 TaskList
的測試案例失敗。

到目前為止,我們僅處理了透過 props 接受資料和回呼的組件。當您的組件連接到 API 或具有內部狀態時,事情會變得更加棘手。接下來,我們將研究如何隔離和測試此類連接的組件。
具狀態的複合組件
InboxScreen
使用自訂 Hook 從 Taskbox API 提取資料並管理應用程式狀態。與單元測試非常相似,我們希望將組件與真實後端分離,並隔離測試功能。

這就是 Storybook 擴充套件的用武之地。它們允許您模擬 API 請求、狀態、上下文、提供者以及組件所依賴的任何其他內容。The Guardian 和 Sidewalk Labs (Google) 的團隊使用它們來隔離建構整個頁面。
對於 InboxScreen,我們將使用 Mock Service Worker (MSW) 在網路層級攔截請求並傳回模擬回應。
安裝 msw 及其 storybook 擴充套件。
yarn add -D msw msw-storybook-addon
然後,在您的 public 資料夾中產生一個新的 service worker。
npx msw init public/
透過將此程式碼新增到您的 ./storybook/preview.js
檔案中,在 Storybook 中啟用 MSW 擴充套件
import { addDecorator } from '@storybook/react';
import { initialize, mswDecorator } from 'msw-storybook-addon';
initialize();
addDecorator(mswDecorator);
最後,重新啟動 yarn storybook
命令。我們都已設定好在故事中模擬 API 請求。
InboxScreen
呼叫 useTasks
Hook,而 Hook 又從 /tasks
端點提取資料。我們可以使用 msw
參數指定模擬回應。請注意,您可以為每個故事傳回不同的回應。
// InboxScreen.stories.js
import React from 'react';
import { rest } from 'msw';
import { InboxScreen } from './InboxScreen';
import { Default as TaskListDefault } from './components/TaskList.stories';
export default {
component: InboxScreen,
title: 'InboxScreen',
};
const Template = (args) => <InboxScreen {...args} />;
export const Default = Template.bind({});
Default.parameters = {
msw: [
rest.get('/tasks', (req, res, ctx) => {
return res(ctx.json(TaskListDefault.args));
}),
],
};
export const Error = Template.bind({});
Error.args = {
error: 'Something',
};
Error.parameters = {
msw: [
rest.get('/tasks', (req, res, ctx) => {
return res(ctx.json([]));
}),
],
};

狀態有很多不同的形式。某些應用程式使用 Redux 和 MobX 等程式庫或透過發出 GraphQL 查詢來全域追蹤狀態位元。或者它們可能會使用容器組件。Storybook 具有足夠的彈性來支援所有這些情境。有關此主題的更多資訊,請參閱:用於管理資料和狀態的 Storybook 擴充套件。
隔離建構組件降低了開發的複雜性。您不必啟動後端、以使用者身分登入並在 UI 中點擊來除錯某些 CSS。您可以將所有內容設定為一個故事並開始執行。您甚至可以在這些故事上執行自動化迴歸測試。
捕捉迴歸錯誤
在我之前的視覺化測試文章中,我們花了一些時間設定 Chromatic 並複習了基本工作流程。Chromatic 會捕獲每個故事的快照,並將其與現有的基準進行比較。您會看到視覺差異,您可以批准或拒絕。
現在我們有了所有複合組件的故事,我們可以透過執行以下命令來執行視覺化測試
npx chromatic --project-token=<project-token>
您應該會看到包含 TaskList 和 InboxScreen 故事的差異。

現在嘗試變更 Task 組件中的某些內容,例如字型大小或背景顏色。然後提交變更並重新執行 Chromatic。

應用程式的樹狀結構性質表示,對 Task 組件的任何調整也會被更高層級組件的測試捕捉到。測試複合組件可讓您在部署到生產環境之前捕捉到錯誤。
結論
鑑於現代應用程式的規模,開發人員不可能知道組件在哪裡被使用。因此,您經常會意外地發布錯誤。這會拖累您的進度 - 在生產環境中修復這些錯誤需要5-10 倍的時間。組合測試讓我們能夠了解小變更對較大系統的潛在影響。您可以在錯誤像滾雪球般變成重大迴歸錯誤之前捕捉到它們。
接下來,我們將深入探討測試互動。當使用者勾選任務時,您如何確保觸發了適當的事件並且狀態已正確更新?加入郵件列表,以在發布更多 UI 測試文章時收到通知。
阻止錯誤的連鎖反應!
— Storybook (@storybookjs) 2021年7月14日
應用程式是組件相互連接的樹狀結構。如果一個組件失敗,則其周圍的其他組件也會損壞。
組合測試可讓您了解每個小錯誤對整個系統的影響。並將其扼殺在萌芽狀態。https://#/bqMyQD8MNR pic.twitter.com/nrdjDWeNWw