ConstructiCat Logo
CodeBust.
Browse section ▾

Избыточная проверка.

Избыточная проверка сравнивает значение с самим собой или с литералом, равным ему по построению, поэтому её исход предопределён, и она в принципе не может ни упасть, ни обнаружить регрессию.

##Signs and Symptoms

Проверка избыточна, когда её результат предрешён ещё до запуска тестируемого кода: ожидаемый и фактический операнды — это одно и то же значение, либо оба являются литералами, заведомо равными (или неравными). Каталог xUnit/Test Smells (Peruma и др.) определяет её как «тестовый метод, содержащий инструкцию проверки, в которой ожидаемый и фактический параметры совпадают», и отмечает, что такая проверка поэтому «либо всегда истинна, либо всегда ложна».

Как её распознать:

  • assertEquals/toBe/toEqual, у которого оба аргумента — это одно и то же выражение или переменная.
  • Булев литерал, проверяемый против самого себя, например assertTrue(true) или expect(true).toBe(true).
  • Сравнение, константность которого может доказать компилятор/линтер, вроде expect(x === x).toBe(true).
  • «Проверка на вменяемость», которая повторяет только что объявленную константу вместо того, чтобы задействовать систему.
// Запах: исход предопределён, тестируемый код вообще не задействован
test('user is active', () => {
  expect(true).toBe(true);            // всегда проходит
  const status = 'active';
  expect(status).toBe('active');      // повторяет литерал, ничего не доказывает
  expect(user.id).toEqual(user.id);   // значение сравнивается с самим собой
});

Надёжный признак: можно полностью удалить продакшен-код, а проверка всё равно останется зелёной.

##Reasons for the Problem

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

  • Забытая отладка. Каталог прямо отмечает, что этот запах «вносится разработчиками в отладочных целях, а затем забывается», — жёстко зашитая заглушка assertTrue(true), доживающая до коммита.
  • Дрейф из-за копипаста / рефакторинга. В обе стороны assertEquals подставлена одна и та же переменная, или тестируемое значение переименовали так, что ожидаемое и фактическое схлопнулись в один и тот же символ.
  • Тавтология по построению. Значение проверяют против того же литерала, из которого его только что присвоили, вместо независимо выведенного ожидания.
  • Театр покрытия. Проверку добавляют только ради соблюдения правила «в каждом тесте должна быть проверка», ничего осмысленного при этом не проверяя.

Чем это вредит

  • Ложная уверенность. Тест навсегда зелёный и учитывается в размере набора и покрытии, но не проверяет ничего. Он не способен поймать регрессию и потому маскирует прорехи в страховочной сетке.
  • Надёжность бессмысленна. Тест, который никогда не может упасть, даёт нулевой сигнал; тот, что всегда ложен, — мёртвый груз, который игнорируют или ставят skip.
  • Читаемость. Читатели зря тратят силы, восстанавливая предполагаемое поведение по проверке, утверждающей тавтологию; тест больше не документирует требование.
  • Сопровождаемость. Избыточные проверки накапливают шум, раздувают метрики и подрывают доверие к набору тестов, побуждая людей перестать внимательно читать проверки.

##Treatment

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

Шаги:

  1. Найдите проверку с предопределённым исходом — один и тот же операнд с обеих сторон или два равных литерала.
  2. Определите настоящее намерение. Какое поведение этот тест должен был проверять? Если никакое, то проверка (или весь тест) мертва и подлежит удалению.
  3. Проверяйте выход SUT, а не вход. Подайте системе реальный вход и сравните вычисленный ею результат с жёстко зашитым, вычисленным вручную ожидаемым значением — а не с одним из её собственных входов/переменных.
  4. Держите ожидаемое и фактическое различными. Убедитесь, что ожидаемый операнд — это намеренно написанная вами константа, а фактический операнд — возвращаемое значение тестируемого кода (заодно это устраняет родственный запах неверного порядка аргументов).
  5. Перезапустите со сломанной реализацией (внесите мутацию), чтобы убедиться, что проверка действительно может упасть.
// До — избыточно: исход предопределён
test('discount', () => {
  const total = 100;
  expect(total).toBe(100);          // повторяет литерал
  expect(applyDiscount).toBe(applyDiscount); // значение против самого себя
});

// После — осмысленно: известный вход -> независимо ожидаемый выход
test('applies a 10% discount', () => {
  expect(applyDiscount(100, 0.1)).toBe(90); // результат SUT против вручную вычисленного ожидания
});

Если заглушка вроде assertTrue(true) осталась от отладки — удалите её; если она замещала реальную проверку — напишите эту проверку.

##Detected by

  • sonar javascript:S5863Проверкам не следует передавать один и тот же аргумент дважды
  • sonar java:S5863Проверкам не следует передавать один и тот же аргумент дважды
  • eslint no-constant-binary-expressionЗапрет выражений, в которых операция не влияет на значение (помечает самосравнения / всегда истинные проверки вроде assert(a === a))