Picasso:如何測試組件庫

已發表: 2022-03-11

最近發布了 Toptal 設計系統的新版本,它要求我們對 Picasso(我們的內部組件庫)中的幾乎每個組件進行更改。 我們的團隊面臨著一個挑戰:我們如何確保不會發生回歸?

毫不奇怪,簡短的回答是測試。 很多測試。

我們不會回顧測試的理論方面,也不會討論不同類型的測試、它們的用處,或者解釋為什麼你應該首先測試你的代碼。 我們的博客和其他人已經涵蓋了這些主題。 相反,我們將只關注測試的實際方面。

繼續閱讀以了解 Toptal 的開發人員如何編寫測試。 我們的存儲庫是公開的,因此我們使用真實世界的示例。 沒有任何抽像或簡化。

測試金字塔

我們本身並沒有定義測試金字塔,但如果我們這樣做了,它看起來像這樣:

測試金字塔圖

Toptal 的測試金字塔說明了我們強調的測試。

單元測試

單元測試易於編寫且易於運行。 如果您幾乎沒有時間編寫測試,那麼它們應該是您的首選。

然而,它們並不完美。 無論您選擇哪個測試庫(在我們的例子中是 Jest 和 React 測試庫 [RTL]),它都沒有真正的 DOM,並且不允許您在不同的瀏覽器中檢查功能,但它允許您剝離消除複雜性並測試庫的簡單構建塊。

單元測試不僅通過測試代碼的行為來增加價值,而且還通過檢查代碼的整體可測試性來增加價值。 如果您不能輕鬆編寫單元測試,那麼您的代碼可能很糟糕。

視覺回歸測試

即使你有 100% 的單元測試覆蓋率,這並不意味著組件在設備和瀏覽器中看起來都很好。

手動測試特別難以發現視覺回歸。 例如,如果按鈕的標籤移動了 1px,QA 工程師會注意到嗎? 值得慶幸的是,對於這個可見性有限的問題,有很多解決方案。 您可以選擇企業級一體化解決方案,例如 LambdaTest 或 Mabl。 您可以將 Percy 之類的插件合併到您現有的測試中,以及 Loki 或 Storybook 之類的 DIY 解決方案(這是我們在 Picasso 之前使用的)。 它們都有缺點:有些過於昂貴,而另一些則學習曲線陡峭或需要太多維護。

八寶來救援! 它是 Percy 的直接競爭對手,但價格便宜得多,支持更多瀏覽器,並且更易於使用。 另一個大賣點? 它支持 Cypress 集成,這很重要,因為我們希望擺脫使用 Storybook 進行可視化測試。 我們發現自己必須創建故事以確保可視化測試覆蓋率,而不是因為我們需要記錄該用例。 這污染了我們的文檔,使它們更難理解。 我們想將可視化測試與可視化文檔隔離開來。

集成測試

即使兩個組件都有單元測試和可視化測試,也不能保證它們可以一起工作。 例如,我們發現了一個錯誤,即工具提示在下拉項目中使用時無法打開,但在單獨使用時效果很好。

為確保組件良好集成,我們使用了賽普拉斯的實驗性組件測試功能。 起初,我們對性能不佳感到不滿,但我們能夠通過自定義 webpack 配置對其進行改進。 結果? 我們能夠使用賽普拉斯出色的 API 編寫高性能測試,確保我們的組件能夠很好地協同工作。

應用測試金字塔

這一切在現實生活中是什麼樣子的? 讓我們測試 Accordion 組件!

您的第一反應可能是打開編輯器並開始編寫代碼。 我的建議? 花一些時間了解組件的所有功能並寫下您想要涵蓋的測試用例。

Picasso 組件庫演示 GIF

測試什麼?

以下是我們的測試應涵蓋的案例細分:

  • 狀態- 手風琴可以展開和折疊,可以配置其默認狀態,並且可以禁用此功能
  • 樣式- 手風琴可以有邊框變化
  • 內容——它們可以與圖書館的其他單元集成
  • 自定義– 組件的樣式可以被覆蓋,並且可以有自定義展開圖標
  • 回調- 每次狀態更改時,都可以調用回調

Picasso 組件庫演示 GIF - 手風琴組件

如何測試?

現在我們知道我們必須測試什麼,讓我們考慮如何去做。 我們的測試金字塔有三個選項。 我們希望在金字塔各部分之間重疊最小的情況下實現最大覆蓋。 測試每個測試用例的最佳方法是什麼?

  • 狀態- 單元測試可以幫助我們評估狀態是否相應改變,但我們還需要視覺測試來確保組件在每個狀態下都正確呈現
  • 樣式——視覺測試應該足以檢測不同變體的回歸
  • 內容——可視化和集成測試的組合是最佳選擇,因為 Accordions 可以與許多其他組件結合使用
  • 自定義——我們可以使用單元測試來驗證類名是否正確應用,但我們需要一個可視化測試來確保組件和自定義樣式協同工作
  • 回調——單元測試非常適合確保調用正確的回調

手風琴測試金字塔

單元測試

完整的單元測試套件可以在這裡找到。 我們已經介紹了所有狀態更改、自定義和回調:

 it('toggles', async () => { const handleChange = jest.fn() const { getByText, getByTestId } = renderAccordion({ onChange: handleChange, expandIcon: <span data-test /> }) fireEvent.click(getByTestId('accordion-summary')) await waitFor(() => expect(getByText(DETAILS_TEXT)).toBeVisible()) fireEvent.click(getByTestId('trigger')) await waitFor(() => expect(getByText(DETAILS_TEXT)).not.toBeVisible()) fireEvent.click(getByText(SUMMARY_TEXT)) await waitFor(() => expect(getByText(DETAILS_TEXT)).toBeVisible()) expect(handleChange).toHaveBeenCalledTimes(3) })

視覺回歸測試

視覺測試位於此賽普拉斯描述塊中。 屏幕截圖可以在 Happo 的儀表板中找到。

您可以看到所有不同的組件狀態、變體和自定義都已記錄。 每次打開 PR 時,CI 都會將 Happo 存儲的屏幕截圖與在您的分支中截取的屏幕截圖進行比較:

 it('renders', () => { mount( <TestingPicasso> <TestAccordion /> </TestingPicasso> ) cy.get('body').happoScreenshot() }) it('renders disabled', () => { mount( <TestingPicasso> <TestAccordion disabled /> <TestAccordion expandIcon={<Check16 />} /> </TestingPicasso> ) cy.get('body').happoScreenshot() }) it('renders border variants', () => { mount( <TestingPicasso> <TestAccordion borders='none' /> <TestAccordion borders='middle' /> <TestAccordion borders='all' /> </TestingPicasso> ) cy.get('body').happoScreenshot() })

集成測試

我們在這個 Cypress describe 塊中編寫了一個“壞路徑”測試,它斷言 Accordion 仍然可以正常工作,並且用戶可以與自定義組件進行交互。 我們還添加了視覺斷言以增加信心:

 describe('Accordion with custom summary', () => { it('closes and opens', () => { mount(<AccordionCustomSummary />) toggleAccordion() getAccordionContent().should('not.be.visible') cy.get('[data-testid=accordion-custom-summary]').happoScreenshot() toggleAccordion() getAccordionContent().should('be.visible') cy.get('[data-testid=accordion-custom-summary]').happoScreenshot() }) // … })

持續集成

Picasso 幾乎完全依賴 GitHub Actions 進行 QA。 此外,我們為暫存文件的代碼質量檢查添加了 Git 掛鉤。 我們最近從 Jenkins 遷移到 GHA,所以我們的設置仍處於 MVP 階段。

工作流按順序在遠程分支中的每個更改上運行,集成和可視化測試是最後一個階段,因為它們的運行成本最高(無論是在性能還是金錢成本方面)。 除非所有測試都成功完成,否則無法合併拉取請求。

這些是 GitHub Actions 每次都會經歷的階段:

  1. 依賴安裝
  2. 版本控制——驗證提交的格式和 PR 標題是否與常規提交匹配
  3. Lint – ESlint 確保高質量的代碼
  4. TypeScript 編譯——驗證沒有類型錯誤
  5. 包編譯——如果無法編譯包,則無法成功發布; 我們的賽普拉斯測試也期望編譯代碼
  6. 單元測試
  7. 集成和視覺測試

完整的工作流程可以在這裡找到。 目前,完成所有階段只需不到 12 分鐘。

可測試性

像大多數組件庫一樣,Picasso 有一個根組件,它必須包裝所有其他組件,並且可以用來設置全局規則。 這使得編寫測試變得更加困難,原因有兩個——測試結果的不一致,取決於包裝器中使用的道具; 和額外的樣板:

 import { render } from '@testing-library/react' describe('Form', () => { it('renders', () => { const { container } = render( <Picasso loadFavicon={false} environment='test'> <Form /> </Picasso> ) expect(container).toMatchSnapshot() }) })

我們通過創建一個以全局規則為測試的前提條件的 TestingPicasso 解決了第一個問題。 但是必須為每個測試用例聲明它很煩人。 這就是為什麼我們創建了一個自定義渲染函數,它將傳遞的組件包裝在一個 TestingPicasso 中,並返回 RTL 渲染函數中可用的所有內容。

我們的測試現在更容易閱讀和編寫:

 import { render } from '@toptal/picasso/test-utils' describe('Form', () => { it('renders', () => { const { container } = render(<Form />) expect(container).toMatchSnapshot() }) })

結論

這裡描述的設置遠非完美,但對於那些有足夠冒險精神來創建組件庫的人來說,這是一個很好的起點。 我讀過很多關於測試金字塔的文章,但在實踐中應用它們並不總是那麼容易。 因此,我邀請您探索我們的代碼庫並從我們的錯誤和成功中學習。

組件庫是獨一無二的,因為它們服務於兩種受眾:與 UI 交互的最終用戶和使用您的代碼構建自己的應用程序的開發人員。 在強大的測試框架上投入時間將使每個人受益。 在可測試性改進上投入時間將使您作為維護者和使用(和測試)您的庫的工程師受益。

我們沒有討論諸如代碼覆蓋率、端到端測試以及版本和發布政策之類的事情。 關於這些主題的簡短建議是:經常發布,練習適當的語義版本控制,在您的流程中保持透明度,並為依賴您的庫的工程師設定期望。 我們可能會在後續文章中更詳細地重新討論這些主題。