Ошибки в тестах: галерея антипаттернов и чеклист для ревью
Сцена · галерея антипаттернов
Зелёный набор тестов это ещё не хороший набор. На ревью ты раз за разом встречаешь одни и те же болезни: тест, который проверяет, как код написан, а не что он делает; один гигантский объект, от которого зависит полнабора; ассерт, повторяющий формулу из продакшна; десять моков вокруг одной функции. Каждая из них пройдёт CI и будет светиться зелёным, пока однажды не подведёт.
Эта серия уже разбирала эти ошибки поодиночке: поведение против реализации, эвристики качества, овермок. Здесь мы собираем их в одну галерею с примерами до и после и в готовый чеклист из десяти пунктов, по которому проходишь любой тест перед мерджем, свой и агентский.
Карта урока · что заберёшь
- узнаешь шесть главных антипаттернов теста в лицо, каждый с примером до и после;
- поймёшь, чем опасен рожок мороженого и как его выправить;
- получишь чеклист из десяти пунктов для ревью теста;
- научишься применять чеклист к коду агента, который воспроизводит антипаттерны массово.
Раздел 1 · шесть антипаттернов
Идея словами
У каждого антипаттерна одна и та же беда: тест перестаёт ловить баги, но продолжает выглядеть полезным. Разберём шесть самых частых, на минимальных примерах.
Антипаттерн 1 · тест на реализацию
Тест дёргает приватный метод или проверяет, как именно посчитан результат, а не сам результат. Любой рефактор красит его зря.
// до: привязка к тому, КАК считается
expect(account._internalReducer).toHaveBeenCalledWith('SUBTRACT', 30);
// после: проверяем результат, а не внутренний путь
const next = applyPayment(account, makePayment({ amount: 30 }));
expect(next.balance).toBe(70);
Антипаттерн 2 · тавтология
Ассерт повторяет формулу из продакшна. Если в коде ошибка в формуле, она же приедет в тест, и оба согласятся.
// до: дублирует реализацию, поймает только опечатку
expect(applyPayment(acc, p).balance).toBe(acc.balance - p.amount);
// после: ожидаемое число посчитано человеком, а не формулой
expect(applyPayment(makeAccount({ balance: 100 }), makePayment({ amount: 30 })).balance).toBe(70);
Антипаттерн 3 · вечнозелёный тест
Тест физически не может покраснеть. Чаще всего из-за забытого await или ассерта, который всегда истинен.
// до: промис не дождались, тест зелёный даже при reject
it('отвергает перерасход', () => {
expect(charge(acc, 1000)).rejects.toThrow(); // нет await/return
});
// после: ошибку дождались и проверили как значение
it('отвергает перерасход', async () => {
const result = await Effect.runPromise(charge(acc, 1000).pipe(Effect.result));
expect(result).toBeFailure(PaymentRejected);
});
Вторая, менее очевидная форма того же антипаттерна: if в теле теста. Чаще всего он появляется не по злому умыслу, а ради сужения типа.
// до: настоящие проверки спрятаны в ветку, которая может не выполниться
expect(Result.isFailure(result)).toBe(true);
if (Result.isFailure(result)) {
expect(result.failure._tag).toBe('PaymentRejected');
}
// после: сужение унесено в матчер, ветки нет
expect(result).toBeFailure(PaymentRejected);
Пока первая строка на месте, всё выглядит рабочим. Но ослабь её или потеряй при мердже, и тело if перестанет исполняться: тест не проверяет ничего и при этом зелёный. Правило простое: в тесте не должно быть условий. Если условие понадобилось ради типов, его место в матчере (29-testing/06 · Раннер и ассерты), а не в тесте.
Антипаттерн 4 · god-фикстура
Один огромный объект на все тесты. Меняешь одно поле, краснеет половина набора, которая на это поле даже не смотрела (урок 07). Лечится factory с overrides: в тесте видно только важное поле.
Антипаттерн 5 · овермок
Замокано всё вокруг функции, тест проверяет, что код вызвал то, что вызвал, и краснеет от любого рефактора (урок 13). Лечится in-memory fake (в Effect это тестовый Layer).
Антипаттерн 6 · тест без причины падать
Несколько ассертов в одном тесте проверяют разное, и неясно, что именно сломалось при падении. Один тест, один повод падать (AAA).
Когда код пишет агент
Агент воспроизводит эти антипаттерны массово, потому что они “проходят”: тавтология всегда зелёная, овермок всегда зелёный, вечнозелёный по определению. Сгенерировал двадцать тестов, и среди них пяток тавтологий и пара вечнозелёных. Чеклист из следующего раздела это твой фильтр на ревью, а mutation testing автоматический детектор того же.
Что взять с собой
- антипаттерн опасен тем, что выглядит полезным, оставаясь зелёным;
- тест на реализацию, тавтология, вечнозелёный, god-фикстура, овермок, много поводов падать это шесть лиц одной болезни;
- у каждого есть лечение, разобранное в предыдущих уроках.
дальше после входа
Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.
Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.