Логотип ConstructiCat
CodeBust.
Навигация по разделу ▾

Игнорируемый тест.

Тест, который закоммичен в кодовую базу, но никогда не выполняется, потому что он пропущен, отключён или закомментирован, создавая видимость покрытия, ничего на самом деле не проверяя.

##Признаки и симптомы

Тест существует в наборе, но навсегда лишён возможности выполняться. Он по-прежнему считается «тестом» в файле, однако даёт ноль проверки. Высматривайте маркеры skip/ignore, блоки с префиксом x и код тестов, погребённый в комментариях, — нередко в паре с расплывчатой отговоркой вроде «flaky» или «fix later».

// модификаторы skip/disabled — никогда не выполняются
describe.skip('checkout flow', () => { /* ... */ });
it.skip('applies the discount', () => { /* ... */ });

// с префиксом x (jasmine/jest/mocha) — тот же эффект
xit('rejects negative amounts', () => { /* ... */ });
xdescribe('payments', () => { /* ... */ });

// закомментированный тест — невидим И для раннера, И для репортёра
// it('retries on 503', async () => {
//   await expect(client.fetch()).resolves.toBeOk();
// });
// JUnit: @Ignore / @Disabled с расплывчатой причиной
@Ignore("disabled for now as this test is too flaky")
@Test public void peerPriority() { /* ... */ }

Выдающие признаки в отчёте о тестах: ненулевой счётчик «skipped»/«pending», на который никто не смотрит; наборы, тихо съёжившиеся со временем; и пропуски без привязанной задачи или срока истечения. Условные return в начале теста (if (process.platform === 'win32') return;) — более хитрый вариант, полностью скрывающий пропуск от счётчиков пропусков.

Не всякая пометка — этот запах. test.todo('handles expired coupons — PAY-412') не имеет тела, отображается в отчёте как todo (а не как пройденный тест) и ссылается на задачу — это честная, заметная заглушка. А тест с пустым телом, который отчитывается зелёным, — другая проблема: пустой тест.

##Причины проблемы

Почему это происходит

  • Тест начал падать (настоящая регрессия, нестабильная зависимость от времени, смена среды/версии), и пропуск был самым быстрым способом получить зелёную сборку или разблокировать слияние.
  • Тест написали раньше реализации, припарковали через xit/it.skip без задачи и так к нему и не вернулись. (test.todo со ссылкой на задачу хотя бы остаётся на виду в каждом отчёте.)
  • Миграции (новый фреймворк, среда выполнения или API) сломали тест, и его припарковали «временно».
  • Пропуск задумывался на полдня; без напоминания, срока истечения или отслеживающей задачи он становится постоянным.

Почему это вредно

  • Ложная уверенность. Пропущенный тест выглядит как покрытие в файле и в diff пул-реквеста, но ничего не проверяет. Путь кода, который он якобы защищал, теперь без охраны, а все считают его безопасным.
  • Гниение. Пропущенный тест по-прежнему компилируется и проходит проверку типов, но не выполняется, поэтому никто не замечает, когда его фикстуры, ожидаемые значения и допущения перестают соответствовать коду. Закомментированный тест ещё хуже: он даже не компилируется, поэтому изменения API ломают его молча, и это выясняется, только когда кто-нибудь его раскомментирует. Чем дольше такой тест лежит, тем сильнее отходит от реальности, пока его повторное включение не станет дороже переписывания.
  • Скрытые регрессии. Баг или нестабильное поведение, спровоцировавшее пропуск, всё ещё на месте — его просто заглушили. Пропуск лечит симптом (красную полосу), а не болезнь.
  • Шум и эрозия. Постоянные счётчики «пропущено» приучают команду игнорировать отчёт о тестах, из-за чего новые пропуски проскальзывают незамеченными. Мёртвый и закомментированный код тестов также добавляет издержки на чтение и сопровождение без всякой пользы.

##Лечение

Относитесь к каждому игнорируемому тесту как к решению, которое нужно принять сейчас, а не откладывать бесконечно.

  1. Разберите каждый пропуск. Для каждого отключённого или закомментированного теста решите: починить, удалить или поместить в карантин со сроком истечения и отслеживаемой задачей. «Оставить пропущенным навсегда» — не вариант. test.todo со ссылкой на задачу — уже честная заглушка: держите его, только пока задача открыта.
  2. Почините и снова включите, если поведение всё ещё важно. Если тест нестабилен, устраните нестабильность (возьмите под контроль время, случайность, асинхронность и общее состояние), а не пропускайте его.
  3. Удалите его, если функциональность исчезла или тест устарел. Удалённый тест честен; пропущенный лжёт. Система контроля версий помнит его, если он когда-нибудь понадобится, — поэтому никогда не комментируйте тест вместо его удаления.
  4. Если приходится пропустить временно, сделайте это громко и с ограничением по времени: всегда указывайте причину и ссылку на отслеживание, и предпочитайте механизм, который проявляется в отчётах и падает по истечении срока, чтобы пропуск не сгнил.
  5. Остановите кровотечение с помощью линтера/CI. Включите правило «никаких отключённых тестов», чтобы новые пропуски ловились на ревью, и относитесь к существующему счётчику пропусков как к бэклогу, который нужно свести к нулю.
// до — тихо, навсегда, без отслеживания
it.skip('refunds the full amount on cancel', async () => {
  await expect(refund(order)).resolves.toEqual({ amount: 100 });
});

// после — починен и снова работает (коренная причина: итог с плавающей точкой)
it('refunds the full amount on cancel', async () => {
  await expect(refund(order)).resolves.toEqual({ amount: 100 });
});

// допустимый промежуточный вариант — заметный, с указанием автора и сроком истечения
it.skip('refunds the full amount on cancel — flaky clock, see JIRA-1234 (remove by 2027-03-01)', async () => {
  /* ... */
});
// до
@Ignore("too flaky")
@Test public void peerPriority() { ... }

// после: почините зависимость от тайминга и снова включите, или удалите, если устарел
@Test public void peerPriority() { ... }

##Обнаружение анализаторами

  • eslint-jest jest/no-disabled-tests — Запретить отключённые тесты
  • eslint-jest jest/no-commented-out-tests — Запретить закомментированные тесты
  • eslint-vitest vitest/no-disabled-tests — Запретить отключённые тесты
  • eslint-vitest vitest/no-commented-out-tests — Запретить закомментированные тесты
  • sonar java:S1607 — @Ignore / @Disabled должны указывать причину — правило срабатывает только на аннотации без неё
  • sonar javascript:S1607 — Тесты не должны пропускаться без указания причины