Picasso: So testen Sie eine Komponentenbibliothek

Veröffentlicht: 2022-03-11

Kürzlich wurde eine neue Version des Designsystems von Toptal veröffentlicht, was dazu führte, dass wir Änderungen an fast allen Komponenten in Picasso, unserer internen Komponentenbibliothek, vornehmen mussten. Unser Team stand vor einer Herausforderung: Wie stellen wir sicher, dass es nicht zu Regressionen kommt?

Die kurze Antwort lautet, wenig überraschend, Tests. Viele Tests.

Wir werden weder die theoretischen Aspekte des Testens noch die verschiedenen Arten von Tests und ihre Nützlichkeit diskutieren oder erklären, warum Sie Ihren Code überhaupt testen sollten. Unser Blog und andere haben diese Themen bereits behandelt. Stattdessen konzentrieren wir uns ausschließlich auf die praktischen Aspekte des Testens.

Lesen Sie weiter, um zu erfahren, wie Entwickler bei Toptal Tests schreiben. Unser Repository ist öffentlich, daher verwenden wir Beispiele aus der Praxis. Es gibt keine Abstraktionen oder Vereinfachungen.

Testpyramide

Wir haben per se keine Testpyramide definiert, aber wenn wir eine hätten, würde sie so aussehen:

Pyramidenabbildung testen

Die Testpyramide von Toptal veranschaulicht die Tests, die wir hervorheben.

Unit-Tests

Unit-Tests sind einfach zu schreiben und einfach auszuführen. Wenn Sie nur sehr wenig Zeit haben, Tests zu schreiben, sollten sie Ihre erste Wahl sein.

Sie sind jedoch nicht perfekt. Unabhängig davon, welche Testbibliothek Sie wählen (Jest and React Testing Library [RTL] in unserem Fall), wird sie kein echtes DOM haben und es Ihnen nicht erlauben, die Funktionalität in verschiedenen Browsern zu überprüfen, aber es wird Ihnen erlauben, zu strippen Beseitigen Sie die Komplexität und testen Sie die einfachen Bausteine ​​Ihrer Bibliothek.

Unit-Tests bieten nicht nur einen Mehrwert, indem sie das Verhalten des Codes testen, sondern auch die allgemeine Testbarkeit des Codes überprüfen. Wenn Sie Unit-Tests nicht einfach schreiben können, haben Sie wahrscheinlich schlechten Code.

Visuelle Regressionstests

Selbst wenn Sie eine 100-prozentige Einheitentestabdeckung haben, bedeutet das nicht, dass die Komponenten auf allen Geräten und Browsern gut aussehen.

Visuelle Regressionen sind bei manuellen Tests besonders schwer zu erkennen. Wenn beispielsweise die Beschriftung einer Schaltfläche um 1 Pixel verschoben wird, wird ein QA-Ingenieur dies überhaupt bemerken? Zum Glück gibt es viele Lösungen für dieses Problem der eingeschränkten Sicht. Sie können sich für All-in-One-Lösungen der Enterprise-Klasse wie LambdaTest oder Mabl entscheiden. Sie können Plugins wie Percy in Ihre bestehenden Tests integrieren sowie DIY-Lösungen von Loki oder Storybook (was wir vor Picasso verwendet haben). Sie alle haben Nachteile: Einige sind zu teuer, während andere eine steile Lernkurve haben oder zu wartungsintensiv sind.

Happo zur Rettung! Es ist ein direkter Konkurrent von Percy, aber viel billiger, unterstützt mehr Browser und ist einfacher zu verwenden. Ein weiteres großes Verkaufsargument? Es unterstützt die Cypress-Integration, was wichtig war, weil wir Storybook nicht mehr für visuelle Tests verwenden wollten. Wir fanden uns in Situationen wieder, in denen wir Geschichten erstellen mussten, nur um die visuelle Testabdeckung sicherzustellen, und nicht, weil wir diesen Anwendungsfall dokumentieren mussten. Das verschmutzte unsere Dokumente und machte sie schwieriger zu verstehen. Wir wollten visuelle Tests von der visuellen Dokumentation trennen.

Integrationstests

Selbst wenn zwei Komponenten Einheiten- und visuelle Tests haben, ist das keine Garantie dafür, dass sie zusammenarbeiten werden. Beispielsweise haben wir einen Fehler gefunden, bei dem ein Tooltip nicht geöffnet wird, wenn er in einem Dropdown-Element verwendet wird, aber gut funktioniert, wenn er alleine verwendet wird.

Um sicherzustellen, dass sich die Komponenten gut integrieren, haben wir die experimentelle Komponententestfunktion von Cypress verwendet. Anfangs waren wir mit der schlechten Performance unzufrieden, konnten diese aber durch eine individuelle Webpack-Konfiguration verbessern. Das Ergebnis? Wir konnten die hervorragende API von Cypress nutzen, um leistungsstarke Tests zu schreiben, die sicherstellen, dass unsere Komponenten gut zusammenarbeiten.

Anwendung der Testpyramide

Wie sieht das alles im wirklichen Leben aus? Lassen Sie uns die Accordion-Komponente testen!

Ihr erster Instinkt könnte sein, Ihren Editor zu öffnen und mit dem Schreiben von Code zu beginnen. Mein Rat? Nehmen Sie sich etwas Zeit, um alle Funktionen der Komponente zu verstehen, und schreiben Sie auf, welche Testfälle Sie abdecken möchten.

Demo-GIF der Picasso-Komponentenbibliothek

Was testen?

Hier ist eine Aufschlüsselung der Fälle, die unsere Tests abdecken sollten:

  • Zustände – Akkordeons können erweitert und reduziert werden, ihr Standardzustand kann konfiguriert werden und diese Funktion kann deaktiviert werden
  • Stile – Akkordeons können Randvariationen haben
  • Inhalt – Sie können mit anderen Einheiten der Bibliothek integriert werden
  • Anpassung – Die Stile der Komponente können überschrieben werden und es können benutzerdefinierte Erweiterungssymbole vorhanden sein
  • Rückrufe – Jedes Mal, wenn sich der Status ändert, kann ein Rückruf aufgerufen werden

Picasso-Komponentenbibliothek Demo GIF - Akkordeon-Komponente

Wie testen?

Nachdem wir nun wissen, was wir testen müssen, überlegen wir uns, wie wir vorgehen. Wir haben drei Optionen aus unserer Testpyramide. Wir wollen eine maximale Abdeckung mit minimaler Überlappung zwischen den Abschnitten der Pyramide erreichen. Wie testet man jeden Testfall am besten?

  • Zustände – Einheitentests können uns dabei helfen, zu beurteilen, ob sich die Zustände entsprechend ändern, aber wir benötigen auch visuelle Tests, um sicherzustellen, dass die Komponente in jedem Zustand korrekt gerendert wird
  • Stile – Visuelle Tests sollten ausreichen, um Regressionen der verschiedenen Varianten zu erkennen
  • Inhalt – Eine Kombination aus visuellen und Integrationstests ist die beste Wahl, da Akkordeons in Kombination mit vielen anderen Komponenten verwendet werden können
  • Anpassung – Wir können einen Einheitentest verwenden, um zu überprüfen, ob ein Klassenname korrekt angewendet wird, aber wir benötigen einen visuellen Test, um sicherzustellen, dass die Komponente und die benutzerdefinierten Stile zusammen funktionieren
  • Rückrufe – Unit-Tests sind ideal, um sicherzustellen, dass die richtigen Rückrufe aufgerufen werden

Die Akkordeon-Testpyramide

Unit-Tests

Die vollständige Suite von Unit-Tests finden Sie hier. Wir haben alle Statusänderungen, die Anpassung und Rückrufe behandelt:

 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) })

Visuelle Regressionstests

Die Sehtests befinden sich in diesem Cypress-Beschreibungsblock. Die Screenshots sind im Dashboard von Happo zu finden.

Sie können sehen, dass alle verschiedenen Komponentenzustände, Varianten und Anpassungen aufgezeichnet wurden. Jedes Mal, wenn ein PR geöffnet wird, vergleicht CI die Screenshots, die Happo gespeichert hat, mit denen, die in Ihrer Filiale gemacht wurden:

 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() })

Integrationstests

Wir haben in diesem Cypress-Describe-Block einen „Bad Path“-Test geschrieben, der behauptet, dass das Accordion immer noch korrekt funktioniert und dass Benutzer mit der benutzerdefinierten Komponente interagieren können. Wir haben auch visuelle Behauptungen für zusätzliches Vertrauen hinzugefügt:

 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() }) // … })

Kontinuierliche Integration

Picasso verlässt sich bei der Qualitätssicherung fast ausschließlich auf GitHub Actions. Darüber hinaus haben wir Git-Hooks für Code-Qualitätsprüfungen von Staging-Dateien hinzugefügt. Wir sind kürzlich von Jenkins zu GHA migriert, unser Setup befindet sich also noch in der MVP-Phase.

Der Workflow wird bei jeder Änderung im Remote-Zweig in sequentieller Reihenfolge ausgeführt, wobei die Integration und die visuellen Tests die letzte Stufe darstellen, da sie am teuersten in der Ausführung sind (sowohl in Bezug auf die Leistung als auch auf die finanziellen Kosten). Wenn nicht alle Tests erfolgreich abgeschlossen wurden, kann die Pull-Anforderung nicht zusammengeführt werden.

Dies sind die Phasen, die GitHub Actions jedes Mal durchläuft:

  1. Abhängigkeitsinstallation
  2. Versionskontrolle – Validiert, dass das Format von Commits und PR-Titel mit herkömmlichen Commits übereinstimmt
  3. Lint – ESlint sorgt für qualitativ hochwertigen Code
  4. TypeScript-Kompilierung – Stellen Sie sicher, dass keine Typfehler vorhanden sind
  5. Paketkompilierung – Wenn die Pakete nicht erstellt werden können, werden sie nicht erfolgreich veröffentlicht; Unsere Cypress-Tests erwarten auch kompilierten Code
  6. Unit-Tests
  7. Integrations- und visuelle Tests

Den vollständigen Arbeitsablauf finden Sie hier. Derzeit dauert es weniger als 12 Minuten, um alle Phasen zu absolvieren.

Testbarkeit

Wie die meisten Komponentenbibliotheken hat Picasso eine Root-Komponente, die alle anderen Komponenten umschließen muss und zum Festlegen globaler Regeln verwendet werden kann. Dies macht es aus zwei Gründen schwieriger, Tests zu schreiben – Inkonsistenzen in den Testergebnissen, abhängig von den im Wrapper verwendeten Requisiten; und zusätzliche Textbausteine:

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

Wir haben das erste Problem gelöst, indem wir ein TestingPicasso erstellt haben, das die globalen Regeln für das Testen vorkonditioniert. Aber es ist ärgerlich, es für jeden Testfall deklarieren zu müssen. Aus diesem Grund haben wir eine benutzerdefinierte Renderfunktion erstellt, die die übergebene Komponente in ein TestingPicasso umschließt und alles zurückgibt, was von der RTL-Renderfunktion verfügbar ist.

Unsere Tests sind jetzt einfacher zu lesen und einfacher zu schreiben:

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

Fazit

Das hier beschriebene Setup ist alles andere als perfekt, aber es ist ein guter Ausgangspunkt für diejenigen unter Ihnen, die abenteuerlustig genug sind, um eine Komponentenbibliothek zu erstellen. Ich habe viel über das Testen von Pyramiden gelesen, aber es ist nicht immer einfach, sie in der Praxis anzuwenden. Daher lade ich Sie ein, unsere Codebasis zu erkunden und aus unseren Fehlern und Erfolgen zu lernen.

Komponentenbibliotheken sind einzigartig, da sie zwei Arten von Zielgruppen bedienen: die Endbenutzer, die mit der Benutzeroberfläche interagieren, und die Entwickler, die Ihren Code verwenden, um ihre eigenen Anwendungen zu erstellen. Die Investition von Zeit in ein robustes Test-Framework wird allen zugute kommen. Wenn Sie Zeit in die Verbesserung der Testbarkeit investieren, profitieren Sie als Betreuer und die Ingenieure, die Ihre Bibliothek verwenden (und testen).

Wir haben Dinge wie Codeabdeckung, End-to-End-Tests und Versions- und Release-Richtlinien nicht besprochen. Der kurze Rat zu diesen Themen lautet: Veröffentlichen Sie häufig, üben Sie eine ordnungsgemäße semantische Versionierung, sorgen Sie für Transparenz in Ihren Prozessen und setzen Sie Erwartungen an die Ingenieure, die sich auf Ihre Bibliothek verlassen. Wir können diese Themen in späteren Beiträgen noch einmal ausführlicher behandeln.