Strumenti del mestiere

I test automatici sono fondamentalmente solo codice che genera o genera un errore se qualcosa non va. La maggior parte delle librerie o dei framework di test offre che semplificano la scrittura dei test.

Come accennato nella sezione precedente, queste primitive includono quasi sempre un modo per definire test indipendenti (chiamati casi di test) e fornire le asserzioni. Le asserzioni sono un modo per combinare il controllo di un risultato e l'invio di una se qualcosa non va e può essere considerata la primitiva di base di tutte di test delle primitive.

Questa pagina illustra un approccio generale a queste primitive. Il framework scelto probabilmente contiene qualcosa di simile, ma non si tratta di un riferimento esatto.

Ad esempio:

import { fibonacci, catalan } from '../src/math.js';
import { assert, test, suite } from 'a-made-up-testing-library';

suite('math tests', () => {
  test('fibonacci function', () => {
    // check expected fibonacci numbers against our known actual values
    // with an explanation if the values don't match
    assert.equal(fibonacci(0), 0, 'Invalid 0th fibonacci result');
    assert.equal(fibonacci(13), 233, 'Invalid 13th fibonacci result');
  });
  test('relationship between sequences', () => {
    // catalan numbers are greater than fibonacci numbers (but not equal)
    assert.isAbove(catalan(4), fibonacci(4));
  });
  test('bugfix: check bug #4141', () => {
    assert.isFinite(fibonacci(0)); // fibonacci(0) was returning NaN
  })
});

In questo esempio viene creato un gruppo di test (a volte chiamato suite) chiamato "matematica test" e definisce tre scenari di test indipendenti in cui ciascuno esegue alcune asserzioni. Questi scenari di test possono in genere essere gestiti singolarmente o eseguiti, ad esempio, da un il flag di filtro nel runner del test.

Supporti delle asserzioni come primitivi

La maggior parte dei framework di test, incluso Vitest, include una raccolta di asserzioni helper su un oggetto assert che ti consentono di controllare rapidamente i valori restituiti altri stati contro alcune aspettative. Quella aspettativa è spesso un "bene noto" e i relativi valori. Nell'esempio precedente, sappiamo che il tredicesimo numero di Fibonacci dovrebbe essere 233. Possiamo confermarlo, quindi, direttamente tramite assert.equal.

Potreste anche avere aspettative che un valore assuma una certa forma o che maggiore di un altro valore o avere qualche altra proprietà. Questo corso non l'intera gamma di possibili aiutanti per le asserzioni, ma i framework di test effettua sempre almeno i seguenti controlli di base:

  • Una "verità" controllo, spesso descritto come "ok" verifica che una condizione sia vera corrispondente a come potresti scrivere un if che verifica se qualcosa ha esito positivo oppure risposta esatta. Generalmente fornito nel formato assert(...) o assert.ok(...). accetta un singolo valore più un commento facoltativo.

  • Una verifica di uguaglianza, come nel caso del test matematico, in cui ci si aspetta restituiscono il valore o lo stato di un oggetto in modo che corrisponda a un valore valido noto. Si tratta di uguaglianza primitiva (ad esempio per numeri e stringhe) o uguaglianza referenziale (sono lo stesso oggetto). Dietro le quinte, queste sono solo una "verità" verifica con un confronto di == o ===.

    • JavaScript fa distinzione tra uguaglianza bassa (==) e rigida (===). La maggior parte delle librerie di test fornisce i metodi assert.equal e rispettivamente assert.strictEqual.
  • Controlli di uguaglianza profonda, che estendono i controlli di uguaglianza per includere la verifica contenuti di oggetti, array e altri tipi di dati più complessi, oltre al logica interna per attraversare gli oggetti per confrontarli. Sono importanti perché JavaScript non ha un modo incorporato per confrontare i contenuti due oggetti o array. Ad esempio, [1,2,3] == [1,2,3] è sempre false. Testa spesso includono gli helper deepEqual o deepStrictEqual.

Assistente per le asserzioni che confrontano due valori (anziché un solo controllo di verifica veritiera) in genere accetta due o tre argomenti:

  • Il valore effettivo generato dal codice sottoposto a test o che descrive il per la convalida.
  • Il valore previsto, solitamente hardcoded (ad esempio, un numero letterale o ).
  • Un commento facoltativo che descrive cosa previsto che verrà incluso in caso di errore di questa riga.
di Gemini Advanced.

Inoltre, è abbastanza comune combinare le asserzioni per creare una perché è raro che sia possibile confermare correttamente lo stato del il sistema da solo. Ad esempio:

  test('JWT parse', () => {
    const json = decodeJwt('eyJieSI6InNhbXRob3Ii');

    assert.ok(json.payload.admin, 'user should be admin');
    assert.deepEqual(json.payload.groups, ['role:Admin', 'role:Submitter']);
    assert.equal(json.header.alg, 'RS265')
    assert.isAbove(json.payload.exp, +new Date(), 'expiry must be in future')
  });

Vitest utilizza la libreria delle asserzioni Chai internamente per fornire i suoi assistenti alle asserzioni ed è utile esaminare il suo riferimento per vedere quali asserzioni e helper potrebbero essere adatti al tuo codice.

Asserzioni fluide e BDD

Alcuni sviluppatori preferiscono uno stile di asserzione che può essere chiamato basato sul comportamento sviluppo (BDD) o Stile fluente le asserzioni. Questi messaggi sono detti anche "previsti" perché il punto di accesso il controllo delle aspettative è un metodo denominato expect().

Aspettati che gli aiutanti si comportino allo stesso modo delle asserzioni scritte come un metodo semplice. chiamate come assert.ok o assert.strictDeepEquals, ma alcuni sviluppatori le trovano più facili da leggere. Un'asserzione BDD potrebbe essere riassunta nel seguente modo:

// A failure here would generate "Expect result to be an array that does include 42"
const result = await possibleMeaningsOfLife();
expect(result).to.be.an('array').that.does.include(42);

// or a simpler form
expect(result).toBe('array').toContainEqual(42);

// the same in assert might be
assert.typeOf(result, 'array', 'Expected the result to be an array');
assert.include(result, 42, 'Expected the result to include 42');

Questo tipo di asserzioni funziona grazie a una tecnica chiamata concatenamento di metodi, in cui l'oggetto restituito da expect può essere continuamente concatenato con per altre chiamate al metodo. Alcune parti della chiamata, tra cui to.be e that.does nell'esempio precedente, non hanno funzioni e sono inclusi solo per effettuare la chiamata più facile da leggere e potenzialmente generare un commento automatico se non riuscito. Tieni presente che expect normalmente non supporta un commento facoltativo, il concatenamento deve descrivere l'errore in modo chiaro.

Molti framework di test supportano sia Fluent/BDD sia le asserzioni regolari. Vitest ad esempio, esporta entrambi L'approccio di Chai e il suo approccio leggermente più conciso al BDD. Jest, d'altra parte include solo una aspettativa metodo predefinito.

Raggruppa i test tra i file

Quando scriviamo i test, tendiamo già a fornire raggruppamenti impliciti, piuttosto che rispetto a tutti i test in un unico file, capita di scrivere test su più . Infatti, i test runner sanno solo che un file è da testare perché di un filtro predefinito o di un'espressione regolare, ad esempio vitest include tutte file del progetto che terminano con un'estensione come ".test.jsx" o ".spec.ts" (".test" e ".spec" più una serie di estensioni valide).

I test dei componenti tendono a trovarsi in un file di confronto con il componente sottoposto a test. come nella seguente struttura di directory:

Un elenco di file in un
  tra cui UserList.tsx e UserList.test.tsx.
Un file dei componenti e il relativo file di test.

Analogamente, i test delle unità tendono a essere posizionati accanto al codice sottoposto a test. I test end-to-end possono trovarsi ciascuno nel proprio file e i test di integrazione essere inseriti in cartelle proprie. Queste strutture possono essere utili scenari di test complessi crescono fino a richiedere file di supporto non di test, come le librerie di supporto necessarie solo per un test.

Raggruppa i test all'interno dei file

Come negli esempi precedenti, è pratica comune eseguire test all'interno di una chiamata a suite() che raggruppa i test che hai configurato con test(). Le suite di solito non sono ma aiutano a fornire la struttura raggruppando i test correlati o obiettivi, chiamando il metodo passato. Per test(), il metodo passato descrive le azioni del test stesso.

Come per le asserzioni, c'è un'equivalenza abbastanza standard in Fluent/BDD per che raggruppano i test. Alcuni esempi tipici sono confrontati nel seguente codice:

// traditional/TDD
suite('math tests', () => {
  test('handle zero values', () => {
    assert.equal(fibonacci(0), 0);
  });
});

// Fluent/BDD
describe('math tests', () => {
  it('should handle zero values', () => {
    expect(fibonacci(0)).toBe(0);
  });
})

Nella maggior parte dei framework, suite e describe si comportano in modo simile, così come test e it, in contrapposizione alle maggiori differenze tra l'utilizzo di expect e assert scrivere asserzioni.

Altri strumenti hanno approcci leggermente diversi all'organizzazione delle suite e dei test. Per ad esempio, l'esecutore del test integrato di Node.js supporta le chiamate nidificate a test() per di creare implicitamente una gerarchia di test. Tuttavia, Vitest consente solo questo tipo di nidificazione utilizzando suite() e non verrà eseguita una test() definita all'interno di un altro test().

Come per le asserzioni, ricorda che l'esatta combinazione di raggruppamento i metodi forniti dal tuo stack tecnico non sono così importanti. Questo corso tratterà in forma astratta, ma dovrai capire come si applicano la scelta degli strumenti.

Metodi del ciclo di vita

Un motivo per raggruppare i test, anche implicitamente al livello superiore di un è fornire metodi di configurazione ed eliminazione eseguiti per ogni test, oppure una volta per un gruppo di test. La maggior parte dei framework offre quattro metodi:

Per ogni "test()" o "it()" Una volta per la suite
Prima dell'esecuzione del test "beforeOgni()" `beforeAll()`
Dopo l'esecuzione del test "afterOgni()" "afterAll()"

Ad esempio, potresti voler precompilare un database utenti virtuale prima di test e poi cancellalo:

suite('user test', () => {
  beforeEach(() => {
    insertFakeUser('bob@example.com', 'hunter2');
  });
  afterEach(() => {
    clearAllUsers();
  });

  test('bob can login', async () => {  });
  test('alice can message bob', async () => {  });
});

Questo può essere utile per semplificare i test. Puoi condividere configurazioni comuni il codice di eliminazione, invece di duplicarlo in ogni test. Inoltre, se il codice di configurazione e di smontaggio genera un errore, che può indicare problemi che non implicano l'esito negativo dei test stessi.

Indicazioni generali

Ecco alcuni suggerimenti da ricordare quando pensi a queste primitive.

Le primitive sono una guida

Ricorda che gli strumenti e le primitive qui e nelle prossime pagine non corrisponde esattamente a Vitest, Jest, Mocha, Web Test Runner o qualsiasi altro framework specifico. Abbiamo utilizzato Vitest come guida generale, ma assicurati di creare una mappa al framework che preferisci.

Combina e abbina le asserzioni in base alle esigenze

I test sono fondamentalmente del codice che può generare errori. Ogni runner ti fornirà primitive, probabilmente test(), per descrivere scenari di test distinti.

Ma se questo runner fornisce anche assert(), expect() e aiutanti per le asserzioni, ricorda che questa parte riguarda più la comodità e puoi saltarla se necessario. Puoi eseguire qualsiasi codice che potrebbe generare un errore, tra cui altri librerie di asserzioni o un'affermazione if tradizionale.

La configurazione IDE può essere una salvezza

Assicurarsi che l'IDE, come VSCode, abbia accesso al completamento automatico la documentazione sugli strumenti di test che hai scelto può aumentare la tua produttività. Per Ad esempio, esistono più di 100 metodi su assert nell'asserzione Chai libreria di Google e disporre della documentazione per quella a destra appare in linea può essere conveniente.

Ciò può essere particolarmente importante per alcuni framework di test che popolano lo spazio dei nomi globale con i relativi metodi di test. Si tratta di una sottile differenza, Spesso è possibile utilizzare le librerie di test senza importarle, se sono aggiunti automaticamente allo spazio dei nomi globale:

// some.test.js
test('using test as a global', () => {  });

Consigliamo di importare gli assistenti anche se sono supportati automaticamente, perché questo offre al tuo IDE un modo chiaro per cercare questi metodi. (Potresti avere riscontrato questo problema durante la creazione di React, dato che alcuni codebase hanno React è globale, ma alcuni non lo sono e richiedono che venga importato in tutti i file utilizzando React.)

// some.test.js
import { test } from 'vitest';
test('using test as an import', () => {  });