Picasso:コンポーネントライブラリをテストする方法
公開: 2022-03-11Toptalの設計システムの新しいバージョンが最近リリースされたため、社内のコンポーネントライブラリであるPicassoのほぼすべてのコンポーネントに変更を加える必要がありました。 私たちのチームは課題に直面しました。回帰が起こらないようにするにはどうすればよいでしょうか。
簡単な答えは、当然のことながら、テストです。 たくさんのテスト。
テストの理論的側面を確認したり、さまざまな種類のテストやその有用性について説明したり、最初にコードをテストする必要がある理由を説明したりすることはありません。 私たちのブログや他の人たちはすでにそれらのトピックをカバーしています。 代わりに、テストの実際的な側面のみに焦点を当てます。
Toptalの開発者がどのようにテストを書くかを学ぶために読んでください。 私たちのリポジトリは公開されているので、実際の例を使用します。 抽象化や単純化はありません。
ピラミッドのテスト
テストピラミッド自体は定義されていませんが、定義すると次のようになります。
Toptalのテストピラミッドは、私たちが強調するテストを示しています。
ユニットテスト
単体テストは簡単に記述でき、実行も簡単です。 テストを書く時間がほとんどない場合は、テストを最初に選択する必要があります。
ただし、それらは完全ではありません。 選択したテストライブラリ(この場合はJest and React Testing Library [RTL])に関係なく、実際のDOMはなく、さまざまなブラウザーで機能を確認することはできませんが、削除することはできます。複雑さを取り除き、ライブラリの単純な構成要素をテストします。
単体テストは、コードの動作をテストするだけでなく、コードの全体的なテスト容易性をチェックすることによっても価値を付加します。 単体テストを簡単に記述できない場合は、コードが不良である可能性があります。
視覚的回帰テスト
単体テストのカバレッジが100%であっても、コンポーネントがデバイスやブラウザー間で見栄えがよいとは限りません。
手動テストでは、視覚的な退行を見つけるのは特に困難です。 たとえば、ボタンのラベルが1px移動した場合、QAエンジニアは気付くことさえありますか? ありがたいことに、視界が制限されているというこの問題には多くの解決策があります。 LambdaTestやMablなどのエンタープライズグレードのオールインワンソリューションを選択できます。 Percyなどのプラグインを既存のテストに組み込んだり、LokiやStorybook(Picassoより前に使用していたもの)などのDIYソリューションを組み込んだりすることができます。 それらにはすべて欠点があります。高価すぎるものもあれば、学習曲線が急なものや、メンテナンスが多すぎるものもあります。
救助にハッポ! これはPercyの直接の競合相手ですが、はるかに安価で、より多くのブラウザーをサポートし、使いやすくなっています。 もう一つの大きなセールスポイントは? サイプレスの統合をサポートします。これは、ストーリーブックを視覚的なテストに使用することをやめたかったため、重要でした。 ユースケースを文書化する必要があるためではなく、視覚的なテストカバレッジを確保するためだけに、ストーリーを作成する必要がある状況に陥りました。 それは私たちのドキュメントを汚染し、それらを理解することをより困難にしました。 ビジュアルテストをビジュアルドキュメントから分離したかったのです。
統合テスト
2つのコンポーネントにユニットテストとビジュアルテストがある場合でも、それらが連携して機能することを保証するものではありません。 たとえば、ドロップダウンアイテムで使用するとツールチップが開かないが、単独で使用するとうまく機能するというバグが見つかりました。
コンポーネントが適切に統合されるようにするために、サイプレスの実験的なコンポーネントテスト機能を使用しました。 最初はパフォーマンスの低下に不満がありましたが、カスタムWebpack構成で改善することができました。 結果? サイプレスの優れたAPIを使用して、コンポーネントが適切に連携することを確認するパフォーマンステストを作成することができました。
テストピラミッドの適用
これは実際の生活ではどのように見えますか? アコーディオンコンポーネントをテストしてみましょう!
あなたの最初の本能は、エディターを開いてコードを書き始めることかもしれません。 私のアドバイス? コンポーネントのすべての機能を理解し、カバーしたいテストケースを書き留めてください。
何をテストしますか?
テストでカバーする必要があるケースの内訳は次のとおりです。
- 状態–アコーディオンは展開および折りたたむことができ、デフォルトの状態を構成でき、この機能を無効にすることができます
- スタイル–アコーディオンには境界線のバリエーションがあります
- コンテンツ–ライブラリの他のユニットと統合できます
- カスタマイズ–コンポーネントのスタイルを上書きしたり、カスタムの展開アイコンを使用したりできます
- コールバック–状態が変化するたびに、コールバックを呼び出すことができます
テストする方法は?
何をテストする必要があるかがわかったので、それをどのように実行するかを考えてみましょう。 テストピラミッドには3つのオプションがあります。 ピラミッドのセクション間のオーバーラップを最小限に抑えて、最大のカバレッジを実現したいと考えています。 各テストケースをテストするための最良の方法は何ですか?
- 状態–単体テストは、状態がそれに応じて変化するかどうかを評価するのに役立ちますが、コンポーネントが各状態で正しくレンダリングされることを確認するための視覚的なテストも必要です。
- スタイル–さまざまなバリアントの回帰を検出するには、視覚的なテストで十分です。
- コンテンツ–アコーディオンは他の多くのコンポーネントと組み合わせて使用できるため、視覚的テストと統合テストの組み合わせが最良の選択です。
- カスタマイズ–単体テストを使用してクラス名が正しく適用されているかどうかを確認できますが、コンポーネントとカスタムスタイルが連携して機能することを確認するための視覚的なテストが必要です
- コールバック–ユニットテストは、適切なコールバックが呼び出されることを確認するのに理想的です
アコーディオンテストピラミッド
ユニットテスト
ユニットテストの完全なスイートはここにあります。 すべての状態の変更、カスタマイズ、およびコールバックについて説明しました。
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() })統合テスト
このサイプレスのdescribeブロックに「不良パス」テストを記述しました。これは、アコーディオンが引き続き正しく機能し、ユーザーがカスタムコンポーネントを操作できることを表明します。 また、信頼性を高めるために視覚的なアサーションを追加しました。
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は、QAのGitHubアクションにほぼ完全に依存しています。 さらに、ステージングされたファイルのコード品質チェック用のGitフックを追加しました。 最近、JenkinsからGHAに移行したため、セットアップはまだMVP段階にあります。
ワークフローは、リモートブランチのすべての変更に対して順番に実行されます。統合テストとビジュアルテストは、実行に最もコストがかかるため(パフォーマンスと金銭的コストの両方の点で)、最終段階になります。 すべてのテストが正常に完了しない限り、プルリクエストをマージすることはできません。
GitHubActionsが毎回実行する段階は次のとおりです。
- 依存関係のインストール
- バージョン管理–コミットの形式とPRタイトルが従来のコミットと一致することを検証します
- Lint –ESlintは高品質のコードを保証します
- TypeScriptコンパイル–タイプエラーがないことを確認します
- パッケージのコンパイル–パッケージをビルドできない場合、それらは正常にリリースされません。 サイプレスのテストでは、コンパイルされたコードも必要です
- ユニットテスト
- 統合と視覚的テスト
完全なワークフローはここにあります。 現在、すべてのステージを完了するのに12分もかかりません。
妥当性
ほとんどのコンポーネントライブラリと同様に、Picassoには、他のすべてのコンポーネントをラップする必要があり、グローバルルールを設定するために使用できるルートコンポーネントがあります。 これにより、2つの理由でテストを作成することが難しくなります。ラッパーで使用されている小道具によっては、テスト結果に矛盾が生じます。 および追加のボイラープレート:
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を操作するエンドユーザーと、コードを使用して独自のアプリケーションを構築する開発者の2種類の対象者にサービスを提供するという点で独特です。 堅牢なテストフレームワークに時間を投資することは、すべての人に利益をもたらします。 テスト容易性の改善に時間を投資することは、ライブラリを使用(およびテスト)するメンテナおよびエンジニアとしてのあなたに利益をもたらします。
コードカバレッジ、エンドツーエンドのテスト、バージョンとリリースのポリシーなどについては説明しませんでした。 これらのトピックに関する簡単なアドバイスは、頻繁にリリースし、適切なセマンティックバージョニングを実践し、プロセスに透明性を持たせ、ライブラリに依存するエンジニアに期待を設定することです。 これらのトピックについては、以降の投稿でさらに詳しく説明する可能性があります。
