Passa al contenuto

Testing

Perché testare?

I test automatizzati aiutano te e il tuo team a sviluppare rapidamente e con fiducia applicazioni complesse in Vue, prevenendo regressioni ed incoraggiandoti a suddividere la tua applicazione in funzioni, moduli, classi e componenti testabili. Come per qualsiasi applicazione, la tua nuova app Vue può rompersi in molti modi ed è importante che tu possa individuare questi problemi e correggerli prima di rilasciare.

In questa guida, copriremo la terminologia di base e forniremo le nostre raccomandazioni su quali strumenti scegliere per la tua applicazione Vue 3.

C'è una sezione specifica su Vue che riguarda i composabili. Vedi Testing Composables qui sotto per ulteriori dettagli.

Quando testare

Inizia a testare presto! Ti consigliamo di iniziare a scrivere test il prima possibile. Più attendi ad aggiungere i test alla tua applicazione, più dipendenze avrà l'applicazione e più difficile sarà iniziare.

Tipi di test

Quando progetti la strategia di test per la tua applicazione Vue, dovresti sfruttare i seguenti tipi di test:

  • Unità: Verifica che gli input a una determinata funzione, classe o componibile producano l'output o gli effetti collaterali attesi.
  • Componente: Verifica che il tuo componente venga montato, renderizzato, possa essere interagito e si comporti come previsto. Questi test importano più codice rispetto ai test di unità, sono più complessi e richiedono più tempo per essere eseguiti.
  • End-to-end: Verifica le funzionalità che coinvolgono più pagine e effettuano richieste di rete reali alla tua applicazione Vue costruita in produzione. Questi test spesso coinvolgono l'avvio di un database o di altri backend.

Ogni tipo di test gioca un ruolo nella strategia di test della tua applicazione e ognuno ti proteggerà da diversi tipi di problemi.

Panoramica

Discuteremo brevemente cosa sono ognuno di questi tipi di test, come possono essere implementati per le applicazioni Vue e fornire alcune raccomandazioni generali.

Unit Test

I test di unità sono scritti per verificare che piccole unità isolate di codice funzionino come previsto. Un test di unità copre di solito una singola funzione, classe, componibile o modulo. I test di unità si concentrano sulla correttezza logica e si occupano solo di una piccola parte della funzionalità complessiva dell'applicazione. Possono simulare grandi parti dell'ambiente dell'applicazione (ad esempio, lo stato iniziale, classi complesse, moduli di terze parti e richieste di rete).

In generale, i test di unità individueranno problemi con la logica di business di una funzione e la sua correttezza logica.

Prendi ad esempio questa funzione increment:

js
// helpers.js
export function increment (current, max = 10) {
  if (current < max) {
    return current + 1
  }
  return current
}

Poiché è molto autonoma, sarà facile invocare la funzione increment e verificare che restituisca ciò che dovrebbe, quindi scriveremo un test di unità.

Se una qualsiasi di queste asserzioni fallisce, è chiaro che il problema è contenuto nella funzione increment.

js
// helpers.spec.js
import { increment } from './helpers'

describe('increment', () => {
  test('increments the current number by 1', () => {
    expect(increment(0, 10)).toBe(1)
  })

  test('does not increment the current number over the max', () => {
    expect(increment(10, 10)).toBe(10)
  })

  test('has a default max of 10', () => {
    expect(increment(10)).toBe(10)
  })
})

Come precedentemente menzionato, gli unit test vengono generalmente applicati alla logica di business autonoma, ai componenti, alle classi, ai moduli o alle funzioni che non coinvolgono il rendering dell'interfaccia utente, le richieste di rete o altre questioni ambientali.

In genere, questi sono moduli JavaScript / TypeScript puri non correlati a Vue. In generale, scrivere test di unità per la logica di business nelle applicazioni Vue non differisce significativamente dalle applicazioni che utilizzano altri framework.

Ci sono due casi in cui SI effettuano test di unità sulle funzionalità specifiche di Vue:

  1. Composables
  2. Componenti

Composables

Una categoria di funzioni specifiche per le applicazioni Vue sono Composables, che possono richiedere una gestione speciale durante i test. Guarda Testing Composables di seguito per ulteriori dettagli.

Unit test per i componenti

Un componente può essere testato in due modi:

  1. Whitebox: Unit Testing

    I test che sono "test di Whitebox" sono consapevoli dei dettagli di implementazione e delle dipendenze di un componente. Sono focalizzati sull'isolamento del componente in prova. Questi test solitamente coinvolgono la simulazione di alcuni, se non tutti, i figli del componente, nonché la configurazione dello stato dei plugin e delle dipendenze (ad esempio, Pinia).

  2. Blackbox: Test dei Componenti

    I test che sono "test di Blackbox" non sono a conoscenza dei dettagli di implementazione di un componente. Questi test simulano il minor numero possibile di elementi per testare l'integrazione del componente e dell'intero sistema. Solitamente, renderizzano tutti i componenti figli e vengono considerati più come un "test di integrazione". Vedere le raccomandazioni per il testing dei componenti di seguito.

Raccomandazione

  • Vitest

    Poiché la configurazione ufficiale creata da create-vue si basa su Vite, consigliamo di utilizzare un framework per i test di unità che possa sfruttare la stessa configurazione e la stessa pipeline di trasformazione direttamente da Vite. Vitest è un framework per i test di unità progettato appositamente per questo scopo, creato e mantenuto da membri del team Vue / Vite. Si integra facilmente con i progetti basati su Vite ed è estremamente veloce.

Altre opzioni

  • Peeky è un altro rapido runner per i test di unità con un'integrazione di prima classe con Vite. È stato anche creato da un membro del team Vue core e offre un'interfaccia grafica per i test.

  • Jest è un framework popolare per i test di unità e può essere utilizzato con Vite tramite il pacchetto vite-jest. Tuttavia, consigliamo Jest solo se hai già una suite di test Jest che deve essere migrata in un progetto basato su Vite, poiché Vitest offre un'integrazione più fluida e migliori prestazioni.

Testing dei Componenti

Nelle applicazioni Vue, i componenti sono i principali blocchi di costruzione dell'interfaccia utente. Pertanto, i componenti sono l'unità naturale di isolamento quando si tratta di convalidare il comportamento dell'applicazione. Dal punto di vista della granularità, il testing dei componenti si trova da qualche parte sopra il testing delle unità e può essere considerato una forma di testing di integrazione. Gran parte della tua applicazione Vue dovrebbe essere coperta da un test di componente e ti consigliamo di creare un file di specifica (spec) per ciascun componente Vue.

I test dei componenti dovrebbero individuare problemi relativi alle props del componente, agli eventi, agli slot che fornisce, agli stili, alle classi, ai lifecycle hook e altro ancora.

I test dei componenti non dovrebbero simulare componenti figlio, ma testare invece le interazioni tra il tuo componente e i suoi figli interagendo con i componenti come farebbe un utente. Ad esempio, un test di componente dovrebbe fare clic su un elemento come farebbe un utente anziché interagire con il componente in modo programmato.

I test dei componenti dovrebbero concentrarsi sulle interfacce pubbliche del componente piuttosto che sui dettagli di implementazione interni. Per la maggior parte dei componenti, l'interfaccia pubblica è limitata a: eventi emessi, props e slot. Durante il testing, ricorda di testare ciò che fa un componente, non come lo fa.

FARE

  • Per la logica visuale: verifica l'output di rendering corretto in base alle props e agli slot inseriti.
  • Per la logica comportamentale: verifica gli aggiornamenti di rendering corretti o gli eventi emessi in risposta agli eventi di input dell'utente.

Nell'esempio seguente, mostriamo un componente Stepper che ha un elemento DOM etichettato "increment" e può essere cliccato. Passiamo una prop chiamata max che impedisce al componente Stepper di essere incrementato oltre 2, quindi se facciamo clic sul pulsante 3 volte, l'interfaccia utente dovrebbe comunque mostrare 2.

Non sappiamo nulla dell'implementazione di Stepper, sappiamo solo che l'"input" è la prop max e l'"output" è lo stato del DOM come lo vedrà l'utente.

Vue Test Utils
Cypress
Testing Library
js
const { getByText } = render(Stepper, {
  props: {
    max: 1
  }
})

getByText('0')  // Verifica implicita che "0" sia presente nel componente

const button = getByText('increment')

// Simula un evento di clic sul nostro pulsante di incremento.
await fireEvent.click(button)

getByText('1')

await fireEvent.click(button)
js
const valueSelector = '[data-testid=stepper-value]'
const buttonSelector = '[data-testid=increment]'

const wrapper = mount(Stepper, {
  props: {
    max: 1
  }
})

expect(wrapper.find(valueSelector).text()).toContain('0')

await wrapper.find(buttonSelector).trigger('click')

expect(wrapper.find(valueSelector).text()).toContain('1')
js
const valueSelector = '[data-testid=stepper-value]'
const buttonSelector = '[data-testid=increment]'

mount(Stepper, {
  props: {
    max: 1
  }
})

cy.get(valueSelector).should('be.visible').and('contain.text', '0')
  .get(buttonSelector).click()
  .get(valueSelector).should('contain.text', '1')
  • NON FARE

    Non fare asserzioni sullo stato privato di un'istanza di componente o sui metodi privati di un componente. Testare dettagli di implementazione rende i test fragili, in quanto sono più inclini a rompersi e richiedono aggiornamenti quando cambia l'implementazione.

    Il compito principale di un componente è rappresentare correttamente l'output DOM, quindi i test che si concentrano sull'output DOM offrono lo stesso livello di garanzia di correttezza (se non di più), pur essendo più robusti e resilienti ai cambiamenti.

    Non fare affidamento esclusivo sui test di snapshot. Assegnare stringhe HTML non descrive la correttezza. Scrivi test con intenzionalità.

    Se un metodo deve essere testato approfonditamente, considera l'estrazione in una funzione di utilità autonoma e scrivi un test unitario dedicato per essa. Se non può essere estratto in modo pulito, può essere testato come parte di un componente, di un test di integrazione o end-to-end che lo copra.

Raccomandazione

  • Vitest per componenti o composable che vengono resi in modo "headless" (ad esempio la funzione useFavicon in VueUse). I componenti e il DOM possono essere testati usando @vue/test-utils.

  • Cypress Component Testing per componenti il cui comportamento atteso dipende dal rendering corretto degli stili o dall'attivazione degli eventi nativi del DOM. Può essere utilizzato con Testing Library tramite @testing-library/cypress.

Le principali differenze tra Vitest e i runner basati su browser sono la velocità e il contesto di esecuzione. In breve, i runner basati su browser, come Cypress, possono individuare problemi che i runner basati su node, come Vitest, non possono individuare (ad esempio problemi di stile, eventi nativi reali del DOM, cookie, archiviazione locale e errori di rete), ma i runner basati su browser sono ordini di grandezza più lenti di Vitest perché aprono un browser, compilano i fogli di stile e altro ancora. Cypress è un runner basato su browser che supporta il testing dei componenti. Leggi la pagina di confronto di Vitest per le informazioni più recenti sul confronto tra Vitest e Cypress.

Librerie di montaggio

Il testing dei componenti spesso comporta il montaggio del componente in isolamento, la simulazione di eventi di input dell'utente e la verifica dell'output DOM renderizzato. Esistono librerie di utilità dedicate che semplificano queste operazioni.

  • @vue/test-utils è la libreria ufficiale per il testing dei componenti a basso livello, scritta per fornire agli utenti accesso alle API specifiche di Vue. È anche la libreria a livello inferiore su cui è costruita @testing-library/vue.

  • @testing-library/vue è una libreria di testing Vue focalizzata sul testing dei componenti senza fare affidamento sui dettagli di implementazione. Il suo principio guida è che più i test assomigliano al modo in cui il software viene utilizzato, maggiore fiducia possono fornire.

Raccomandiamo di utilizzare @vue/test-utils per testare i componenti nelle applicazioni.@testing-library/vue ha problemi nel testare componenti asincroni con Suspense, quindi dovrebbe essere usato con cautela.

Altre opzioni

  • Nightwatch è un runner di test end-to-end con il supporto per il Vue Component Testing. (Progetto esempio)

  • WebdriverIO per il testing dei componenti su browser multipli che si basa su interazioni utente native basate su automazione standardizzata. Può anche essere utilizzato con Testing Library.

Testing E2E

Sebbene i test unitari offrano ai programmatori un certo grado di sicurezza, i test unitari e i test dei componenti hanno limitazioni nella loro capacità di fornire una copertura completa dell'applicazione quando viene distribuita in produzione. Di conseguenza, i test end-to-end (E2E) forniscono una copertura su ciò che è probabilmente l'aspetto più importante di un'applicazione: cosa succede quando gli utenti effettivamente utilizzano le tue applicazioni.

I test end-to-end si concentrano sul comportamento delle applicazioni multi-pagina che effettuano richieste di rete contro l'applicazione Vue costruita per la produzione. Spesso coinvolgono la configurazione di un database o di un backend e potrebbero persino essere eseguiti contro un ambiente di staging live.

I test end-to-end spesso rilevano problemi relativi al router, alla libreria di gestione dello stato, ai componenti di alto livello (ad esempio un'app o un layout), agli asset pubblici o a qualsiasi gestione delle richieste. Come già detto, rilevano problemi critici che potrebbero essere impossibili da individuare con i test unitari o i test dei componenti.

I test end-to-end non importano alcun codice dell'applicazione Vue, ma si affidano completamente al test dell'applicazione navigando attraverso intere pagine in un vero browser.

I test end-to-end convalidano molte delle componenti dell'applicazione. Possono essere indirizzati all'applicazione costruita localmente o persino a un ambiente di staging live. I test contro l'ambiente di staging includono non solo il codice frontend e il server statico, ma tutti i servizi di backend e l'infrastruttura associata.

Più i tuoi test assomigliano al modo in cui il tuo software viene utilizzato, maggiore fiducia possono darti. - Kent C. Dodds - Autore della Testing Library

Testando come le azioni degli utenti influenzano la tua applicazione, i test E2E sono spesso la chiave per una maggiore fiducia nel corretto funzionamento di un'applicazione.

Scelta di una soluzione di test E2E

Sebbene i test end-to-end (E2E) sul web abbiano guadagnato una cattiva reputazione per i test inaffidabili (instabili) e per il rallentamento dei processi di sviluppo, gli strumenti E2E moderni hanno fatto passi avanti per creare test più affidabili, interattivi e utili. Quando si sceglie un framework di test E2E, le seguenti sezioni forniscono alcune indicazioni su cosa tenere presente nella scelta di un framework di test per la tua applicazione.

Test multi-browser

Uno dei principali vantaggi noti dei test end-to-end (E2E) è la capacità di testare l'applicazione su più browser. Sebbene possa sembrare auspicabile avere una copertura multi-browser del 100%, è importante notare che i test multi-browser hanno un rendimento decrescente sulle risorse del team a causa del tempo aggiuntivo e della potenza della macchina richiesta per eseguirli in modo coerente. Di conseguenza, è importante essere consapevoli di questo compromesso quando si sceglie la quantità di test multi-browser necessari per la tua applicazione.

Feedback più rapido

Uno dei problemi principali legati ai test end-to-end (E2E) e allo sviluppo è che l'esecuzione di tutta la suite richiede molto tempo. Di solito, ciò avviene solo nelle pipeline di integrazione e distribuzione continue (CI/CD). I framework di test E2E moderni hanno contribuito a risolvere questo problema aggiungendo funzionalità come la parallelizzazione, che consente alle pipeline CI/CD di eseguire spesso i test con tempi molto più brevi rispetto a prima. Inoltre, durante lo sviluppo in locale, la possibilità di eseguire selettivamente un singolo test per la pagina su cui si sta lavorando, garantendo anche il ricaricamento a caldo dei test, può contribuire ad aumentare la produttività e il flusso di lavoro di uno sviluppatore.

Esperienza di debug di prima classe

Mentre i programmatori tradizionalmente si affidavano all'analisi dei log in una finestra del terminale per determinare cosa fosse andato storto in un test, i moderni framework di test end-to-end (E2E) consentono ai programmatori di sfruttare strumenti con cui sono già familiari, ad esempio gli strumenti per sviluppatori del browser.

Visibilità in modalità headless

Quando i test end-to-end (E2E) vengono eseguiti nelle pipeline di integrazione / distribuzione continue, spesso vengono eseguiti in browser headless (cioè, nessun browser visibile viene aperto per l'utente). Una caratteristica critica dei moderni framework di test E2E è la capacità di visualizzare snapshot e/o video dell'applicazione durante il test, fornendo una certa comprensione del motivo per cui si verificano errori. Storicamente, mantenere queste integrazioni era laborioso.

Raccomandazione

  • Cypress

    Nel complesso, riteniamo che Cypress fornisca la soluzione E2E più completa, con funzionalità come un'interfaccia grafica informativa, eccellente capacità di debug, asserzioni integrate e stub, resistenza agli errori, parallelizzazione e snapshot. Come menzionato in precedenza, offre anche il supporto per il testing dei componenti. Tuttavia, supporta solo browser basati su Chromium e Firefox.

Altre opzioni

  • Playwright è anche una ottima soluzione di test E2E con un'ampia gamma di supporto per i browser (principalmente WebKit). Guarda Why Playwright per ulteriori dettagli.

  • Nightwatch è una soluzione di test E2E basata su Selenium WebDriver. Questo gli conferisce la più ampia gamma di supporto per i browser.

  • WebdriverIO è un framework di automazione dei test per il web e il testing mobile basato sul protocollo WebDriver.

Ricette

Aggiunta di Vitest a un Progetto

In un progetto Vue basato su Vite, eseguire:

sh
> npm install -D vitest happy-dom @testing-library/vue

Successivamente, aggiornare la configurazione di Vite per aggiungere il blocco di opzioni test:

js
// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  // ...
  test: {
    // abilita le API globali simili a Jest
    globals: true,
    // simula il DOM con happy-dom
    // (richiede l'installazione di happy-dom come dipendenza peer)
    environment: 'happy-dom'
  }
})

TIP

Se si sta utilizzando TypeScript, aggiungere vitest/globals al campo types nel file tsconfig.json.

json
// tsconfig.json

{
  "compilerOptions": {
    "types": ["vitest/globals"]
  }
}

Poi creare un file con estensione *.test.js nel tuo progetto. Puoi posizionare tutti i file di test in una directory di test nella radice del progetto, o in directory di test accanto ai tuoi file sorgente. Vitest li cercherà automaticamente utilizzando la convenzione di denominazione.

js
// MyComponent.test.js
import { render } from '@testing-library/vue'
import MyComponent from './MyComponent.vue'

test('it should work', () => {
  const { getByText } = render(MyComponent, {
    props: {
      /* ... */
    }
  })

  // asserisci l'output
  getByText('...')
})

Infine, aggiornare il file package.json per aggiungere lo script di test e eseguirlo:

json
{
  // ...
  "scripts": {
    "test": "vitest"
  }
}
sh
> npm test

Testare i Composables

Si assume che tu abbia già letto la sezione Composables.

Quando si tratta di testare i composables, possiamo dividerli in due categorie: composables che non dipendono da un'istanza di componente host e composables che invece dipendono da essa.

Un composable dipende da un'istanza di componente host quando utilizza le seguenti API:

  • Lifecycle hooks
  • Provide / Inject

Se un composable utilizza solo le API di reattività, allora può essere testato invocandolo direttamente e verificando il suo stato / metodi restituiti:

js
// counter.js
import { ref } from 'vue'

export function useCounter() {
  const count = ref(0)
  const increment = () => count.value++

  return {
    count,
    increment
  }
}
js
// counter.test.js
import { useCounter } from './counter.js'

test('useCounter', () => {
  const { count, increment } = useCounter()
  expect(count.value).toBe(0)

  increment()
  expect(count.value).toBe(1)
})

Un composable che si basa su hook del ciclo di vita o su Provide / Inject deve essere incapsulato in un'istanza di componente host per essere testato. Possiamo creare un helper come il seguente:

js
// test-utils.js
import { createApp } from 'vue'

export function withSetup(composable) {
  let result
  const app = createApp({
    setup() {
      result = composable()
      // sopprimi l'avviso di mancato template
      return () => {}
    }
  })
  app.mount(document.createElement('div'))
  // restituisci il risultato e l'istanza dell'app
  // per testare provide / unmount
  return [result, app]
}
js
import { withSetup } from './test-utils'
import { useFoo } from './foo'

test('useFoo', () => {
  const [result, app] = withSetup(() => useFoo(123))
  // fai il mock del provide per testare le iniezioni
  app.provide(...)
  // esegui le asserzioni
  expect(result.foo.value).toBe(1)
  // attiva l'hook onUnmounted se necessario
  app.unmount()
})

Per composables più complessi, potrebbe essere più facile testarli scrivendo test contro il componente wrapper utilizzando le tecniche di Component Testing.

Testing has loaded