Пустой тест.
Пустой тест — это тестовый метод, тело которого не содержит ни одной исполняемой инструкции, поэтому он всегда проходит, ничего при этом не проверяя.
##Признаки и симптомы
Тест существует и числится как пройденный, но его тело не содержит исполняемого кода — ни подготовки, ни вызова, ни проверки. Характерные формы:
// Полностью пустое тело
test('calculates tax', () => {});
// Только TODO / комментарий-заглушка
it('should reject invalid input', () => {
// TODO: написать, когда стабилизируется API
});
// Всё закомментировано
it('parses the auth header', () => {
// const result = parse(header);
// expect(result).toEqual(expected);
});
Как его распознать:
- Имя теста обещает поведение, но тело — это
{}, пробелы или одни лишь комментарии. - Сводка раннера показывает тест как пройденный (зелёный), а не пропущенный/отложенный — именно это и делает его опасным.
- Покрытие кода для названной функциональности подозрительно нулевое, хотя «тест для неё» существует.
- При ревью диф добавляет тест-кейс, но ни одного вызова
expect/assert.
Похожий, но иной случай: тест, у которого есть подготовка и вызов тестируемой системы, но нет ни одной проверки, — это запах Тест без проверок / Неизвестный тест. Пустой тест — более крайний случай, когда тело вообще лишено инструкций.
##Причины проблемы
Почему это происходит
- Тест создали как заготовку (в TDD — «сначала напиши имя») и так и не дописали.
- Код закомментировали, чтобы отладить падение или унять флакающий тест, а потом закоммитили и забыли.
- Генератор или IDE выдали скелет-заготовку теста, которую никто не доделал.
- Разработчик хотел «зарезервировать» имя теста на будущее, но вместо явного todo-маркера оставил пустое тело.
Чем это вредит
- Ложная уверенность (самое худшее). Большинство раннеров сообщают о пустом тесте как о пройденном, а не пропущенном. JUnit, Jest, Vitest и им подобные покажут зелёный для
test('x', () => {}). Кажется, что набор тестов покрывает поведение, но регрессия в продакшен-коде так и не будет поймана — это, можно сказать, хуже, чем вообще не иметь теста, ведь зелёная галочка активно вводит в заблуждение. - Читаемость. Читатель видит тест с именем
should reject invalid inputи резонно полагает, что это поведение проверено. Имя документирует намерение, которого тело так и не выполняет. - Сопровождаемость. Пустые тесты раздувают число тестов и создают видимость покрытия «по именам», скрывая настоящие пробелы и мешая отличить намеренные заготовки от заброшенных.
- Подрывает доверие. Стоит участникам команды понять, что «пройден» не значит «проверен», и значимость сигнала всего набора тестов падает.
##Лечение
Решите, должен ли тест вообще пока существовать, а затем заставьте раннер говорить правду.
1. Если поведение нужно тестировать уже сейчас — заполните тело. Добавьте arrange/act/assert, чтобы тест действительно выполнял код:
// До — зелёный, но ничего не проверяет
test('rounds half up', () => {});
// После — настоящая проверка
test('rounds half up', () => {
expect(round(2.5)).toBe(3);
});
2. Если это настоящая заготовка — пометьте её явно как отложенную, чтобы раннер сообщал о ней как о todo/пропущенной (видимой, а не обманчиво зелёной), вместо того чтобы оставлять пустое тело. Todo с номером тикета — видимая и честная заготовка; пропуск без причины и тикета лишь меняет этот запах на игнорируемый тест:
// До
it('should reject invalid input', () => {
// TODO
});
// После — отображается как todo в отчёте, никогда как "пройден"
it.todo('should reject invalid input (PROJ-123)'); // Jest / Vitest — укажите тикет
// test.skip(...) / xit(...) — только с указанной причиной и тикетом, иначе это игнорируемый тест
3. Если тест устарел — удалите его. Удалённый тест честен; пустой — это ложь, требующая сопровождения.
4. Восстановите закомментированную логику. Если тело полностью закомментировано, либо раскомментируйте и почините его, либо удалите тест; никогда не оставляйте закомментированный тестовый код в роли «реализации».
5. Поставьте заслон. Включите в CI правило линтера, требующее наличия проверок (см. детекторы), чтобы пустые тесты и тесты без проверок роняли сборку, а не проходили молча.
##Обнаружение анализаторами
- eslint-jest jest/expect-expect — eslint-plugin-jest: помечает тесты без единого вызова expect() — ловит и пустые тела, и тела только с TODO или закомментированным кодом
- eslint-vitest vitest/expect-expect — eslint-plugin-vitest: та же проверка для Vitest — в каждом тесте должен быть хотя бы один вызов expect()
- eslint no-empty-function — ESLint core: no-empty-function — ловит только буквально пустой колбэк вроде () => {}; функции, содержащие комментарий, правило пустыми не считает, поэтому тела только с TODO или закомментированным кодом проскакивают (для них нужен expect-expect)
- sonar javascript:S2699 — SonarSource: «Тесты должны содержать проверки» (Blocker; помечает тесты без проверок и пустые тесты; есть аналоги для Java/C#/Python)
- pmd UnitTestShouldIncludeAssert — PMD 7 (Java): UnitTestShouldIncludeAssert, ранее JUnitTestsShouldIncludeAssert (помечает тесты JUnit/TestNG без проверок, включая пустые)