Nota del traduttore
Questa è la traduzione del file when-to-use-plan.md. Qui c'è il link dove si confrontano le differenze tra commit di riferimento di questa traduzione e l'ultimo commit di AVA sul branch master (Se si clicca sul link, e non si vede il file when-to-use-plan.md
nella lista dei file modificati, questa traduzione è aggiornata).
Traduzioni: Español, Français, Italiano, 日本語, Português, Русский, 简体中文
Una delle differenze sostanziali tra AVA e tap
/tape
è il comportamento della funzione t.plan()
. In AVA, t.plan()
è solamente usato per verificare che il numero previsto di asserzioni sia rispettato, ma non termina automaticamente il test.
Molti utenti venendo da tap
/tape
sono abituati ad usare t.plan()
abbondantemente in ogni test. In ogni caso, in AVA, non viene considerato una "buona pratica". Invece t.plan()
dovrebbe essere usato in scenari dove potrebbe realmente aggiungere valore al test.
Non è necessario usare t.plan()
nella maggior parte dei test sincroni.
test(t => {
// Sbagliato: non c'è alcuna ramificazione qui - t.plan() non è utile
t.plan(2);
t.is(1 + 1, 2);
t.is(2 + 2, 4);
});
t.plan()
non fornisce alcun valore aggiunto in questo caso, ma aggiunge solo difficoltà se volessi decidere di aggiungere o rimuovere un'asserzione.
test(t => {
t.plan(1);
return somePromise().then(result => {
t.is(result, 'foo');
});
});
Ad una prima occhiata questo test sembra usare giustamente t.plan()
dato che un handler per una promessa asincrona è coinvolta. Ci sono però alcuni problemi con il test:\
-
Probabilmente
t.plan()
è stato usato per proteggersi contro la possibilità chesomePromise()
potrebbe essere rifiutata; Restituire una promessa rifiutata farebbe fallire il test in ogni caso. -
Sarebbe meglio sfruttare la funzionalità
async
/await
;
test(async t => {
t.is(await somePromise(), 'foo');
});
test(t => {
t.plan(2);
return shouldRejectWithFoo().catch(reason => {
t.is(reason.message, 'Hello');
t.is(reason.foo, 'bar');
});
});
In questo caso t.plan()
viene usato per assicurarsi che il codice nel blocco catch
venga eseguito.
Un'alternativa migliore può essere l'utilizzo di t.throws()
e async
/await
, poichè il codice diventa più semplice da leggere e comprendere:
test(async t => {
const reason = await t.throws(shouldRejectWithFoo());
t.is(reason.message, 'Hello');
t.is(reason.foo, 'bar');
});
test(t => {
t.plan(2);
try {
shouldThrow();
} catch (err) {
t.is(err.message, 'Hello');
t.is(err.foo, 'bar');
}
});
Come già detto nellesempio precedente, è meglio utilizzare t.throws()
con async
/await
.
t.plan()
aggiunge valore ai tuoi test nei casi seguenti.
test.cb(t => {
t.plan(2);
const callbackA = () => {
t.pass();
t.end();
};
const callbackB = () => t.pass();
bThenA(callbackA, callbackB);
});
Il codice sopra verifica che callbackB
sia chiamata prima (ed una sola volta), seguita poi da callbackA
. Ogni altra combinazione non soddisferebbe la soglia impostata.
In molti scenari, è una cattiva idea usare complicate ramificazioni nei tuoi test. Un'eccezione particolare riguarda i test che vengono generati automaticamente (per esempio da un file JSON). Qui sotto t.plan()
è usato per garantire la conformità dell'input JSON:
const testData = require('./fixtures/test-definitions.json');
testData.forEach(testDefinition => {
test(t => {
const result = functionUnderTest(testDefinition.input);
// testDefinition dovrebbe avere solo uno tra `foo` o `bar` ma non entrambi
t.plan(1);
if (testDefinition.foo) {
t.is(result.foo, testDefinition.foo);
}
if (testDefinition.bar) {
t.is(result.bar, testDefinition.foo);
}
});
});
Ci sono molti usi validi per t.plan()
, ma questo non vuole dire che può essere usato in modo indiscriminato. Una semplice regola da seguire è usarlo ogni volta che il tuo test non ha un codice con un flusso diretto o facile da comprendere. I testi con asserzioni dentro callback, blocchi if
/then
, blocchi for
/while
e (in certi casi) try
/catch
sono tutti buoni candidati per l'uso di t.plan()
.