Tłumaczenia: Español, Français, Italiano, 日本語, Português, Русский, 简体中文
Jedna główna różnica między AVA a tap
/tape
to zachowanie t.plan()
. W AVA, t.plan()
służy tylko do zapewnienia, że wywoływana jest oczekiwana liczba asercji; nie kończy automatycznie testu.
Wielu użytkowników przechodzi z tap
/tape
są przyzwyczajeni do korzystania t.plan()
często w każdym teście. Jednak w AVA nie uważamy tego za "najlepszą praktykę". Zamiast tego wierzymy że t.plan()
powinien być stosowany tylko w sytuacjach, w których zapewnia pewną wartość.
t.plan()
jest niepotrzebny w większości testów synchronizacji.
test('simple sums', t => {
// BAD: there is no branching here - t.plan() is pointless
t.plan(2);
t.is(1 + 1, 2);
t.is(2 + 2, 4);
});
t.plan()
nie zapewnia tutaj żadnej wartości i stwarza dodatkowe obowiązki, jeśli kiedykolwiek zdecydujesz się dodać lub usunąć asercje.
test('gives foo', t => {
t.plan(1);
return somePromise().then(result => {
t.is(result, 'foo');
});
});
Na pierwszy rzut oka wydaje się, że testy te dobrze wykorzystują t.plan()
ponieważ zaangażowana jest obsługa asynchronicznych obietnic. Istnieje jednak kilka problemów z testem:
-
t.plan()
jest prawdopodobnie używany tutaj w celu ochrony przed taką możliwościąsomePromise()
może zostać odrzucone; Ale odesłanie odrzuconej obietnicy i tak nie powiedzie się. -
Lepiej byłoby skorzystać z
async
/await
test('gives foo', async t => {
t.is(await somePromise(), 'foo');
});
test('rejects with foo', t => {
t.plan(2);
return shouldRejectWithFoo().catch(reason => {
t.is(reason.message, 'Hello');
t.is(reason.foo, 'bar');
});
});
Tutaj użycie t.plan()
stara się zapewnić wykonanie kodu w bloku 'catch'.
Zamiast tego powinieneś skorzystać t.throwsAsync
i async
/await
, ponieważ prowadzi to do bardziej płaskiego kodu, który łatwiej jest zrozumieć:
test('rejects with foo', async t => {
const reason = await t.throwsAsync(shouldRejectWithFoo());
t.is(reason.message, 'Hello');
t.is(reason.foo, 'bar');
});
test('throws', t => {
t.plan(2);
try {
shouldThrow();
} catch (err) {
t.is(err.message, 'Hello');
t.is(err.foo, 'bar');
}
});
Jak stwierdzono w poprzednim przykładzie, używając asercji t.throws()
z async
/await
jest lepszym wyborem.
t.plan()
zapewnia wartość w następujących przypadkach.
test.cb('invokes callbacks', t => {
t.plan(2);
const callbackA = () => {
t.pass();
t.end();
};
const callbackB = () => t.pass();
bThenA(callbackA, callbackB);
});
Powyższe zapewnia że callbackB
wywołuje się pierwszy (i tylko raz)), śledzony przez callbackA
. Żadna inna kombinacja nie spełnia planu.
W większości przypadków używanie złożonych rozgałęzień w testach jest złym pomysłem. Godnym uwagi wyjątkiem są testy generowane automatycznie (być może z dokumentu JSON). Poniżej t.plan ()
służy do zapewnienia poprawności wejścia JSON:
const testData = require('./fixtures/test-definitions.json');
for (const testDefinition of testData) {
test('foo or bar', t => {
const result = functionUnderTest(testDefinition.input);
// testDefinition should have an expectation for `foo` or `bar` but not both
t.plan(1);
if (testDefinition.foo) {
t.is(result.foo, testDefinition.foo);
}
if (testDefinition.bar) {
t.is(result.bar, testDefinition.foo);
}
});
}
t.plan()
ma wiele uzasadnionych zastosowań, ale nie należy ich używać bez rozróżnienia. Dobrą zasadą jest używanie go za każdym razem, gdy test nie ma prostego, łatwego do uzasadnienia przepływu kodu. Testy z asercjami wewnątrz wywołań zwrotnych, deklaracje if
/then
, pętle for
/while
, i (w niektórych przypadkach) bloki try
/catch
, są dobrymi kandydatami na t.plan()
.