
故事即是測試
組件所有行為的單一事實來源

想像一下,在沒有單元測試的情況下編寫複雜的函式。您將必須手動驗證每個情境,一遍又一遍。非常麻煩。然而,大多數團隊都是這樣構建 UI 的。他們調整一些程式碼,然後嘗試手動模擬每個 UI 狀態來檢查它。使用這種隨意的方法很容易遺漏錯誤。
Storybook 允許您採用更有組織的方法來構建 UI。您將組件的用例記錄為故事,然後將這些故事隔離呈現。這些故事就像單元測試,但用於呈現的組件。您實際上可以在瀏覽器中驗證呈現的輸出。
Storybook 是業界標準的組件 UI 開發工作坊。Netflix、Slack、Stripe 和全球數千個團隊都在使用它,因為它讓他們能夠為 UI 採用測試驅動的方法。讓我們看看如何做到。

具備眼球的單元測試
軟體瞬息萬變。您修復錯誤並發布新功能。但是,每次進行更改時都回去驗證每個組件是不現實的。尤其是在您沒有單元測試來協助您的情況下。
對比大多數團隊編寫非 UI 程式碼的方式。每個函式都有一套單元測試。它們在每次推送時執行,以捕捉迴歸錯誤。它們還允許開發人員清楚地思考程式碼在不同情境下需要做什麼。
然而,單元測試在 UI 方面失敗了,因為很難隔離組件,而且大多數測試工具不允許您(開發人員)以視覺方式驗證介面。
要測試函式,您可以使用像 Jest 這樣的框架。它使您能夠編寫定義完善的測試案例,並隔離執行每個案例。值得注意的是,測試在乾淨的環境中執行,而不是在應用程式中。
如果我們嘗試使用此基礎架構來測試組件,我們就會遇到一個大問題。這些測試沒有眼球!它們在 Node 中運行,可能使用 JSDOM。這意味著我們無法查看 UI 並驗證其外觀。為此,我們必須在真實的瀏覽器中單獨呈現每個組件。
我們需要的是編碼組件的貼紙表。
有沒有注意到設計師如何設定 Figma 或 Sketch 檔案?他們通常組裝一個符號網格。每個符號代表組件的一種狀態。每當設計師進行更改時,他們都可以快速驗證所有變體的正確性。

這就是 Storybook 為 UI 開發人員所做的事情。它編錄每個組件及其各種使用方式。組件被隔離呈現,以簡化開發和測試。

故事是視覺測試案例
一個測試包含三件事:設定、動作和斷言。讓我們分解 Storybook 的這個過程
- 設定: 每個故事描述組件的一個用例。您提供該狀態所需的適當 props 和資料。
- 動作: Storybook 在瀏覽器中呈現此組件。
- 斷言: 您以視覺方式檢查故事並手動測試互動。
在開發期間,您可以循環瀏覽故事以執行快速手動驗證。您實際上是在為介面編寫單元測試。一個視覺單元測試!
話雖如此,組件不僅僅是外觀。您還需要驗證其可訪問性、底層邏輯以及它如何融入更大的系統。故事是自動化和其他形式測試的起點。
一次編寫,處處測試
在我們開始之前,值得重新審視組件需要測試的不同特性
- 視覺: 給定一組 props 或狀態,組件是否正確呈現?
- 組合: 多個組件是否協同工作?
- 互動: 事件是否按預期處理?
- 可訪問性: UI 是否可訪問?
- 使用者流程: 跨各種組件的複雜互動是否有效?
有不同的工具可用於檢查這些方面的每一個。如果您獨立測試每個方面,您最終會一遍又一遍地複製組件狀態。這在設定和維護方面都很麻煩。
*.stories
檔案是組件如何使用的記錄。它是組件狀態的事實來源,這也是您想要在 UI 中測試的內容。它們使用基於 ES6 模組的互操作標準編寫,稱為組件故事格式。每個故事都作為 JavaScript 函式匯出,使您能夠將它們與其他工具重複使用。
您可以將它們與 Testing Library 一起使用,以驗證互動和底層邏輯。在 Chromatic 中進行視覺迴歸測試。或使用 Axe 來稽核可訪問性。或使用 Cypress 來測試使用者流程。所有這些都由同一組故事提供支援!

對於比簡單函式更複雜的組件呢?複雜的組件依賴於上下文、資料、應用程式狀態等等。一旦您在 Storybook 中配置了這些,您就可以在其他測試工具中重複使用該配置。
可移植的故事
Storybook 及其擴充功能生態系統允許您模擬資料、狀態甚至 API 響應。這使您能夠隔離構建和測試複雜的連接組件。更重要的是,您在 Storybook 中進行的任何設定都可以移植到其他測試工具。
@storybook/testing-react 和 @storybook/testing-vue 套件提供了實用程式,用於提升所有包裝故事的提供者、上下文和裝飾器。您為隔離組件所做的所有配置都可以在 Jest、Cypress 等中重複使用。這是一個範例
import { render, screen } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Button.stories';
/**
* Every component that is returned maps 1:1 with the stories.
* But, they also contain all decorators from story, meta and global levels.
*/
const { Primary, Secondary } = composeStories(stories);
test('renders primary button with default args', () => {
render(<Primary />);
const buttonElement = screen.getByText(
/Text coming from args in stories file!/i
);
expect(buttonElement).not.toBeNull();
});
test('renders primary button with override props', () => {
// You can override props and they will get merged with values from the Story's args
render(<Primary>Hello world</Primary>);
const buttonElement = screen.getByText(/Hello world/i);
expect(buttonElement).not.toBeNull();
});
當您將測試案例編寫為故事一次時,很容易在頂部疊加任何形式的斷言。
Storybook 是為 UI 測試而生的
在沒有單元測試的情況下開發組件不僅耗時更長,而且不可能捕捉到所有錯誤。通常很難模擬您想要涵蓋的所有測試案例。或者您可能會完全遺漏一種狀態。Storybook 使隔離呈現組件並探索其所有排列組合變得容易。讓您能夠為構建 UI 採用更有組織和測試驅動的方法。
故事檔案是組件所有重要用例的目錄。它是一個可移植的工件,允許您使用其他測試工具(例如 Testing Library、Jest 和 Axe)驗證互動、可訪問性和應用程式邏輯。
故事是組件的事實來源。它們允許您在開發期間快速檢查外觀。使用自動化捕捉迴歸錯誤或檢查功能品質。最後,為您的所有 UI 元素生成必要的文件。
單元測試,但用於 UI
— Storybook (@storybookjs) June 30, 2021
每個故事都有一組固定的輸入,它隔離呈現,並允許您以視覺方式檢查輸出。就像單元測試一樣。
您甚至可以疊加自動化來
✅ 捕捉迴歸錯誤
🩺 檢查功能品質https://#/qG9bTn8Ve6 pic.twitter.com/6PfT1t0m6I