Come scrivere test migliori per le operazioni di trascinamento della selezione nel browser

Pur mantenendolo quadro-agnostico

Foto di Ash Edmonds su Unsplash

Quando si tratta di interazioni comuni tra un utente e un'applicazione Web, di solito è piuttosto semplice simulare tali azioni in un ambiente di test per affermare la corretta funzionalità di un'app. Mi riferisco a cose come fare clic sui pulsanti, compilare moduli, esplorare percorsi ... le solite cose. Tuttavia, ci sono alcune esperienze meno comuni nel Web che sono molto più difficili da testare. Uno di questi è la funzionalità di trascinamento della selezione.

Ciò è in parte dovuto a quanto sia rotta e incoerente l'API di trascinamento della selezione HTML5. Ciò ha portato molti autori di biblioteche a mettere in campo i loro approcci unici al problema, spesso molto diversi tra loro. Ciò significa che l'implementazione di tale funzionalità da sola nella tua app può essere piuttosto impegnativa e per uno sviluppatore inesperto potrebbe essere ancora più difficile scrivere i test automatici adeguati per essa.

Dopo aver trascorso circa un giorno e mezzo nei test, sono costretto a concludere che il modulo di trascinamento della selezione HTML5 non è solo un disastro, è un vero disastro.
- Peter-Paul Koch

Con mia disperazione, l'app a cui sto lavorando in questo momento a tempo pieno ha molte funzionalità legate al trascinamento in tutto il luogo. Per fortuna, però, è stato abbastanza facile grazie al ricco ecosistema di biblioteche che hanno già affrontato questo problema e sembrano averlo risolto.

Tuttavia, testare automaticamente queste funzionalità può essere non banale e mi piacerebbe condividere alcune delle lezioni che ho imparato. Sto usando React e molti frammenti e campioni saranno centrati su React. Ma in realtà, gli stessi concetti potrebbero essere applicati a qualsiasi pila, che è la bellezza di tutto.

L'approccio iniziale

Ok, quindi diciamo che ho bisogno di costruire una tabella con righe trascinabili e trascinabili, simile a questa (a proposito, non farti distrarre troppo dall'implementazione):

Come puoi vedere, sto usando la classica libreria di reazione dell'ormai famoso Dan Abramov. La funzionalità è terminata, quindi come potremmo testarla a questo punto? Se vai alla documentazione, troverai una bella sezione "Test" che probabilmente ti farà brillare gli occhi.

C'è un suggerimento sull'uso del "backend di prova". Fondamentalmente si avvolge il componente decorato usando questo backend invece del solito backend HTML5 che forniscono. Ciò ti consentirà di testarlo al di fuori di un ambiente browser, ovvero senza accesso al DOM.

Quindi nell'ultima frase del paragrafo precedente, ti ho lanciato molti strani concetti: componente decorato, backend, test backend, backend HTML5 ... cosa? Questi sono tutti concetti interni sottostanti di reagire-dnd e dnd-core, tutti relativi a come funziona sotto il cofano. La stessa guida collegata lo ammette e afferma che è la parte meno documentata della libreria a causa di ciò.

Questo significa che devo avere una conoscenza competente di come funziona questa libreria in particolare per poterla testare? Bene, per me, questo è ciò che la documentazione suggerisce. Questo è difficile, perché può essere fuorviante per sviluppatori inesperti.

In sintesi, ho alcune lamentele sul loro approccio suggerito:

  1. Per testare questa funzione, devo avere familiarità con il modo in cui questa libreria in particolare funziona internamente e i suoi dettagli di implementazione.
  2. Per testare questa funzione, devo anche avere familiarità con il funzionamento del "backend di test", che è qualcosa che non devo conoscere in primo luogo per creare una funzionalità di trascinamento della selezione utilizzando questa libreria. Ciò significa che ho ancora un altro set di documentazione da consumare e un'intera altra dimensione di problemi che potrei incontrare che non sono necessariamente condivisi con il normale back-end HTML5 che userei per la mia app.
  3. Il fatto che io abbia una suite completa di test di superamento che utilizza questo approccio non mi garantisce necessariamente che funzioni effettivamente come mi aspetto dal punto di vista dell'utente. Pensaci: nei miei test e in natura, la funzionalità funzionerebbe con interni completamente diversi. E nonostante le migliori intenzioni dei manutentori, questo approccio non si adatta necessariamente bene al resto dell'ecosistema JS e può darti un falso senso di sicurezza.
  4. Se dovessi mai decidere di cambiare l'approccio alla funzionalità e utilizzare invece un'altra libreria, o scriverlo da solo, tutti i miei test diventeranno improvvisamente obsoleti e dovrò riscriverli di nuovo.

Ora, non fraintendetemi, è bello che abbiano fatto di tutto per creare un "backend di prova" in modo che la funzionalità possa essere testata senza il DOM. Questo è sicuramente utile e ha il suo posto. Ma non è qualcosa che consiglierei a causa dei problemi che ho appena elencato.

Quello che sto cercando è il seguente:

  1. Una suite di test che garantirà al massimo grado che la funzionalità funzioni come previsto (non è possibile raggiungere il 100% di certezza senza test end-to-end, non ciò su cui mi sto concentrando al momento). Ciò significa che voglio affermare l'esatto comportamento della funzionalità dal punto di vista dell'utente nei miei test.
  2. Posso scambiare o modificare l'implementazione della funzione (che include qualsiasi libreria utilizzata sotto) in qualsiasi momento con un impatto minimo sui test.
  3. Non devo avere familiarità con l'implementazione della funzionalità per scrivere i test.
  4. Devo solo usare le mie conoscenze già esistenti e familiari del Web e delle API Web in generale per scrivere questi test.

In movimento

Cosa consiglierei allora? Bene, emula nei tuoi test cosa farebbe un utente quando usa l'app. Fondamentalmente sto sostenendo che tu scriva test di integrazione completi per questa funzione invece di test unitari / isolati, come suggerisce la documentazione.

Oggi abbiamo jsdom che ci consente di attivare in memoria un ambiente browser ad alta fedeltà, senza utilizzare un browser reale. Onestamente jsdom è diventato così buono nel corso degli anni che quasi non riesco a vedere alcun motivo per scrivere test di applicazioni Web che provano a non utilizzare o accedere al DOM. Praticamente tutto ciò che puoi fare in una console per sviluppatori di browser può essere fatto in memoria con jsdom, con alcune eccezioni e avvertenze ovviamente, che vedremo tra poco.

NOTA BENE: non sto dicendo che non dovresti mai scrivere unit test o test in questo modo. Certamente ogni scenario e problema è diverso. Indossa il tuo cappello pensante e decidi che cosa è meglio caso per caso!

Ok, come lo facciamo? Semplice, poniti la domanda: cosa farebbe l'utente con la mia applicazione per utilizzare la funzione di trascinamento della selezione e come si comporta il browser quando si verifica? Quando hai questa risposta, basta codificarla in un test usando le normali API DOM rese accessibili da jsdom! Vediamo come un test di trascinamento verso il basso cercherebbe il nostro esempio particolare usando jest:

In questo frammento non c'è assolutamente nulla di reagente o addirittura correlato a React, e non sta nemmeno usando React Test Utils "Simula. Ciò significa che potrei persino cambiare del tutto la mia libreria / framework dell'interfaccia utente per qualcosa come Angular (diamine, anche Backbone, chiunque?) E questo test avrà ancora senso e funzionerà come previsto.

Questo da solo è sufficiente per testare correttamente quel sottoinsieme della funzionalità, tuttavia, ci sono molti altri eventi che si verificano in un browser reale (mouse, mouse, trascinamento, ecc.) Che non hanno avuto un ruolo nella nostra implementazione . Ciò significa che con un'implementazione diversa, è possibile che il test richieda alcune cose aggiunte o rimosse.

(A proposito, Simulare l'utilizzo è apertamente scoraggiato dagli esperti del settore. Inoltre, se hai trascorso più di 5 minuti in uno dei problemi di GitHub relativi al sistema di eventi di Enzyme, vedrai la stessa opinione degli autori stessi lì. sono stati anche commenti sulla sua rimozione nelle prossime versioni).

Le uniche cose che non sono necessariamente evidenti sono:

  • Gli eventi devono essere in bolla, che non è l'impostazione predefinita quando li creiamo manualmente con il costruttore, quindi è necessario impostarli in modo esplicito. Ciò è correlato al funzionamento del sistema di delega degli eventi di React. Potresti pensare che sia un dettaglio di implementazione, ma non è necessariamente così. Gli eventi si gonfiano nel browser quando vengono comunque attivati ​​dall'interazione reale effettiva.
  • Dobbiamo impostare le proprietà clientX e clientY dell'evento, poiché vengono utilizzate per determinare la direzione del trascinamento. Ancora una volta, con altre implementazioni, potrebbero esserci altre proprietà sugli eventi o altri metodi che dovrai correggere per farlo funzionare (come .getBoundingClientRect ()). Ad esempio, se l'implementazione utilizzava qualcosa come .offsetX, .movementX, .top o qualsiasi altra proprietà relativa a dimensioni, posizione e movimento.

E questo è tutto. Abbiamo affrontato tutti i miei problemi e raggiunto tutti gli obiettivi che ci eravamo prefissati. Con poche altre righe di codice, è possibile portare la copertura del test di questo repository a un 100% abbastanza semplice.

Non è bello?

Pensieri finali

Sentiti libero di esplorare l'intera suite di test qui. Ci sono alcune cose aggiuntive per ottenere la copertura al 100%, quindi assicurati di verificarla. Nota che ho scritto tutto in un singolo test solo per brevità.

Qualcos'altro che vorrei menzionare. Un nuovo sviluppatore potrebbe inserire il codice per i test e sapere esattamente cosa sta succedendo. Immagina se i test usassero i test orientati alla reazione, usando tutti i tipi di concetti e dettagli interni ... che sarebbe un enorme muro in faccia e potrebbero rappresentare un ostacolo sostanziale alla loro capacità di contribuire ai test in modo tempestivo. A quel punto dovrebbero andare a leggere la documentazione di reagente-dnd, il codice sorgente di dnd-core e reagire-test-backend ... yikes!

Voglio lasciarti con questo post sul blog di Sophie Alpert, manager del React Core Team di Facebook, che descrive come potrebbero spedire con successo una riscrittura completa compatibile con API degli interni di React dalla versione 15 alla versione 16 in modo sicuro senza una singola modifica. Avviso spoiler: le suite di test complete hanno affermato la funzionalità della libreria dal punto di vista di un estraneo, invece di concentrarsi su dettagli di implementazione o test unitari isolati.

La cosa davvero divertente è che a partire da luglio 2018, tutti i frammenti della documentazione ufficiale di React utilizzavano una versione 0.14 obsoleta e si è scoperto che hanno funzionato esattamente allo stesso modo nella versione 16.x. Questo dimostra semplicemente quale grande lavoro hanno fatto mantenendo la retrocompatibilità, e ciò non sarebbe stato possibile senza quei test ben scritti e mirati!

Bonus: alcuni suggerimenti su come capire come emulare il comportamento del browser

Se ci sono altre funzionalità che vuoi testare in questo modo, ma non sei sicuro di come si comporti il ​​browser quando lo fa accadere, ti suggerisco di consultare l'API monitorEvents di Google Chrome. È follemente utile in questi scenari, specialmente quando non sei sicuro di cosa stia succedendo. Io stesso l'ho usato in questo modo per esplorare la forma degli eventi generati durante il trascinamento della selezione:

monitorEvents (document.body, [
  'MouseDown',
'MouseMove',
'Dragstart',
'DragEnter',
'trascinare sopra',
'far cadere',
'Dragend',
'Mouseup',
// ...
])

In generale, sarebbe estremamente utile se si estrae semplicemente una console di sviluppo del browser e si inizia a giocare con il sistema di eventi fino a quando non si ha la certezza di sapere come funziona. Crea elementi, attiva eventi, spostali, collegali al DOM, staccali, ecc ... tutto ciò che serve! Investire una o poche ore con questo ti servirà per il resto della tua carriera come sviluppatore web. Affare abbastanza dolce nei miei occhi :)