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 formatoassert(...)
oassert.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 metodiassert.equal
e rispettivamenteassert.strictEqual
.
- JavaScript fa distinzione tra uguaglianza bassa (
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 helperdeepEqual
odeepStrictEqual
.
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.
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:
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', () => { … });