Picasso: come testare una libreria di componenti

Pubblicato: 2022-03-11

Di recente è stata rilasciata una nuova versione del sistema di progettazione di Toptal che richiedeva di apportare modifiche a quasi tutti i componenti di Picasso, la nostra libreria di componenti interna. Il nostro team ha dovuto affrontare una sfida: come possiamo garantire che non si verifichino regressioni?

La risposta breve è, piuttosto non sorprendentemente, test. Tanti test.

Non esamineremo gli aspetti teorici del test né discuteremo i diversi tipi di test, la loro utilità, né spiegheremo perché dovresti testare il tuo codice in primo luogo. Il nostro blog e altri hanno già trattato questi argomenti. Invece, ci concentreremo esclusivamente sugli aspetti pratici dei test.

Continua a leggere per scoprire come gli sviluppatori di Toptal scrivono i test. Il nostro repository è pubblico, quindi utilizziamo esempi del mondo reale. Non ci sono astrazioni o semplificazioni.

Piramide di prova

Non abbiamo una piramide di test definita, di per sé, ma se lo facessimo sarebbe simile a questa:

Illustrazione della piramide di prova

La piramide dei test di Toptal illustra i test che sottolineiamo.

Test unitari

Gli unit test sono semplici da scrivere e facili da eseguire. Se hai poco tempo per scrivere i test, dovrebbero essere la tua prima scelta.

Tuttavia, non sono perfetti. Indipendentemente dalla libreria di test che scegli (Jest and React Testing Library [RTL] nel nostro caso), non avrà un vero DOM e non ti consentirà di verificare la funzionalità in diversi browser, ma ti consentirà di rimuovere elimina la complessità e testa i semplici elementi costitutivi della tua libreria.

Gli unit test non solo aggiungono valore testando il comportamento del codice, ma anche controllando la testabilità complessiva del codice. Se non riesci a scrivere facilmente gli unit test, è probabile che tu abbia un codice errato.

Test di regressione visiva

Anche se hai una copertura del 100% degli unit test, ciò non significa che i componenti abbiano un bell'aspetto su tutti i dispositivi e browser.

Le regressioni visive sono particolarmente difficili da individuare con i test manuali. Ad esempio, se l'etichetta di un pulsante viene spostata di 1px, un tecnico del controllo qualità se ne accorgerà? Per fortuna, ci sono molte soluzioni a questo problema di visibilità limitata. Puoi optare per soluzioni all-in-one di livello aziendale come LambdaTest o Mabl. Puoi incorporare plug-in, come Percy, nei tuoi test esistenti, nonché soluzioni fai-da-te come Loki o Storybook (che è quello che abbiamo usato prima di Picasso). Tutti hanno degli svantaggi: alcuni sono troppo costosi mentre altri hanno una curva di apprendimento ripida o richiedono troppa manutenzione.

Happo in soccorso! È un concorrente diretto di Percy, ma è molto più economico, supporta più browser ed è più facile da usare. Un altro grande punto di forza? Supporta l'integrazione di Cypress, che era importante perché volevamo abbandonare l'utilizzo di Storybook per i test visivi. Ci siamo trovati in situazioni in cui dovevamo creare storie solo per poter garantire la copertura dei test visivi, non perché avessimo bisogno di documentare quel caso d'uso. Ciò ha inquinato i nostri documenti e li ha resi più difficili da capire. Volevamo isolare i test visivi dalla documentazione visiva.

Test di integrazione

Anche se due componenti hanno test unitari e visivi, non è garantito che funzionino insieme. Ad esempio, abbiamo riscontrato un bug per cui una descrizione comando non si apre quando viene utilizzata in un elemento a discesa, ma funziona bene se utilizzata da sola.

Per garantire che i componenti si integrino bene, abbiamo utilizzato la funzione di test dei componenti sperimentali di Cypress. All'inizio non eravamo soddisfatti delle scarse prestazioni, ma siamo stati in grado di migliorarle con una configurazione personalizzata del pacchetto web. Il risultato? Siamo stati in grado di utilizzare l'eccellente API di Cypress per scrivere test performanti che garantiscano che i nostri componenti funzionino bene insieme.

Applicazione della piramide dei test

Che aspetto ha tutto questo nella vita reale? Proviamo il componente Fisarmonica!

Il tuo primo istinto potrebbe essere quello di aprire il tuo editor e iniziare a scrivere codice. Il mio consiglio? Dedica un po' di tempo alla comprensione di tutte le caratteristiche del componente e scrivi quali casi di test vuoi trattare.

GIF demo della libreria dei componenti Picasso

Cosa testare?

Ecco una ripartizione dei casi che i nostri test dovrebbero coprire:

  • Stati : le fisarmoniche possono essere espanse e compresse, il suo stato predefinito può essere configurato e questa funzione può essere disabilitata
  • Stili : le fisarmoniche possono avere variazioni di bordo
  • Contenuto : possono integrarsi con altre unità della libreria
  • Personalizzazione : il componente può avere i suoi stili sovrascritti e può avere icone di espansione personalizzate
  • Richiamate : ogni volta che lo stato cambia, è possibile richiamare una richiamata

GIF demo della libreria dei componenti Picasso - componente per fisarmonica

Come testare?

Ora che sappiamo cosa dobbiamo testare, consideriamo come procedere. Abbiamo tre opzioni dalla nostra piramide di test. Vogliamo ottenere la massima copertura con la minima sovrapposizione tra le sezioni della piramide. Qual è il modo migliore per testare ogni test case?

  • Stati : i test unitari possono aiutarci a valutare se gli stati cambiano di conseguenza, ma abbiamo anche bisogno di test visivi per assicurarci che il componente sia visualizzato correttamente in ogni stato
  • Stili – I test visivi dovrebbero essere sufficienti per rilevare le regressioni delle diverse varianti
  • Contenuto – Una combinazione di test visivi e di integrazione è la scelta migliore, poiché Accordions può essere utilizzato in combinazione con molti altri componenti
  • Personalizzazione : possiamo utilizzare uno unit test per verificare se il nome di una classe è applicato correttamente, ma abbiamo bisogno di un test visivo per assicurarci che il componente e gli stili personalizzati funzionino in tandem
  • Callback : gli unit test sono ideali per garantire che vengano richiamati i callback corretti

La piramide dei test della fisarmonica

Test unitari

La suite completa di unit test può essere trovata qui. Abbiamo coperto tutte le modifiche di stato, la personalizzazione e i callback:

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

Test di regressione visiva

I test visivi si trovano in questo blocco di descrizione di Cypress. Gli screenshot possono essere trovati nella dashboard di Happo.

Puoi vedere che sono stati registrati tutti i diversi stati dei componenti, varianti e personalizzazioni. Ogni volta che viene aperta una PR, CI confronta gli screenshot che Happo ha memorizzato con quelli presi nella tua filiale:

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

Test di integrazione

Abbiamo scritto un test di "percorso errato" in questo blocco di descrizione di Cypress che afferma che la fisarmonica funziona ancora correttamente e che gli utenti possono interagire con il componente personalizzato. Abbiamo anche aggiunto affermazioni visive per una maggiore sicurezza:

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

Integrazione continua

Picasso si affida quasi interamente a GitHub Actions per il QA. Inoltre, abbiamo aggiunto gli hook Git per i controlli della qualità del codice dei file in stage. Di recente siamo migrati da Jenkins a GHA, quindi la nostra configurazione è ancora nella fase di MVP.

Il flusso di lavoro viene eseguito su ogni modifica nella filiale remota in ordine sequenziale, con l'integrazione e i test visivi come l'ultima fase perché sono i più costosi da eseguire (sia in termini di prestazioni che di costo monetario). A meno che tutti i test non vengano completati correttamente, la richiesta pull non può essere unita.

Queste sono le fasi che GitHub Actions attraversa ogni volta:

  1. Installazione delle dipendenze
  2. Controllo versione : verifica che il formato dei commit e il titolo PR corrispondano ai commit convenzionali
  3. Lint – ESlint garantisce un codice di buona qualità
  4. Compilazione TypeScript : verifica che non siano presenti errori di tipo
  5. Compilazione dei pacchetti : se i pacchetti non possono essere compilati, non verranno rilasciati correttamente; i nostri test Cypress prevedono anche codice compilato
  6. Test unitari
  7. Integrazione e test visivi

Il flusso di lavoro completo può essere trovato qui. Attualmente, ci vogliono meno di 12 minuti per completare tutte le fasi.

Testabilità

Come la maggior parte delle librerie di componenti, Picasso ha un componente radice che deve racchiudere tutti gli altri componenti e può essere utilizzato per impostare regole globali. Ciò rende più difficile scrivere i test per due motivi: incoerenze nei risultati dei test, a seconda degli oggetti di scena utilizzati nel wrapper; e boilerplate extra:

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

Abbiamo risolto il primo problema creando un TestingPicasso che presuppone le regole globali per il test. Ma è fastidioso doverlo dichiarare per ogni test case. Ecco perché abbiamo creato una funzione di rendering personalizzata che racchiude il componente passato in un TestingPicasso e restituisce tutto ciò che è disponibile dalla funzione di rendering di RTL.

I nostri test ora sono più facili da leggere e semplici da scrivere:

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

Conclusione

La configurazione qui descritta è tutt'altro che perfetta, ma è un buon punto di partenza per quelli di voi abbastanza avventurosi da creare una libreria di componenti. Ho letto molto sul test delle piramidi, ma non è sempre facile applicarle nella pratica. Pertanto, ti invito a esplorare la nostra base di codice e ad imparare dai nostri errori e successi.

Le librerie di componenti sono uniche perché servono due tipi di pubblico: gli utenti finali che interagiscono con l'interfaccia utente e gli sviluppatori che usano il codice per creare le proprie applicazioni. Investire tempo in un solido framework di test andrà a vantaggio di tutti. Investire tempo nei miglioramenti della testabilità gioverà a te come manutentore e agli ingegneri che utilizzano (e testano) la tua libreria.

Non abbiamo discusso di cose come la copertura del codice, i test end-to-end e le politiche di versione e rilascio. Il breve consiglio su questi argomenti è: rilasciate spesso, praticate un corretto controllo delle versioni semantico, fate trasparenza nei vostri processi e stabilite aspettative per gli ingegneri che fanno affidamento sulla vostra libreria. Potremmo rivisitare questi argomenti in modo più dettagliato nei post successivi.