---
title: "Тест с магическими числами"
type: "test-smell"
slug: "magic-number-test"
url: "http://localhost:3000/ru/test-smells/magic-number-test.md"
category: "Запахи неясности"
description: "Тест жёстко прописывает необъяснённые числовые литералы во входных данных и проверках, скрывая, что эти числа означают и откуда они взялись."
---
# Тест с магическими числами

> Тест жёстко прописывает необъяснённые числовые литералы во входных данных и проверках, скрывая, что эти числа означают и откуда они взялись.

## Signs and Symptoms

Вы распознаёте тест с магическими числами, когда аргументы и проверки теста полны голых чисел, смысл и происхождение которых не очевидны из кода. Читателю приходится реконструировать (или просто доверять) то, почему ожидается конкретное значение.

Характерные признаки:

* Числовые литералы появляются прямо как аргументы проверок: `expect(result).toBe(54.13)`, `assertEquals(86400, ttl)`.
* Один и тот же литерал повторяется в подготовке, действии и ожидаемом значении, и ничто не связывает их именем.
* Числа кодируют доменные понятия, которые не прописаны явно (`3600` \= час, `200` \= HTTP OK, `0.0825` \= ставка налога).
* Рядом с числом стоит комментарий, поясняющий его, — признак того, что само число следовало бы назвать.
* Во время ревью люди спрашивают «почему `42`?» или «откуда берётся `54.13`?», и никто не может ответить, не перезапустив код.

```js
// Запах: что такое 8.25? почему 54.13? что за 50 спрятано внутри хелпера?
test('checkout works', () => {
  const total = checkout(cartFor(50), 8.25);
  expect(total).toBe(54.13);
});

```

Это специфическая, тестовая разновидность общего запаха _магическое число_ (Magic Number) и классический вклад в запах **непрозрачный тест** (Obscure Test) (Месарош): читатель не может понять тест из самого теста.

## Reasons for the Problem

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

* Литерал — путь наименьшего сопротивления: вы вводите значение, увиденное в отладчике, или копируете фактический вывод неудачного прогона прямо в проверку, пока она не станет зелёной («угадай значение» / вставка вывода).
* У автора уже есть доменный контекст в голове, поэтому `3600` или `8.25` кажутся самоочевидными _в момент написания_.
* Значения фикстуры выбираются произвольно (`new User(25, ...)`) лишь для того, чтобы удовлетворить конструктор, без раздумий о смысле.

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

* **Читаемость / намерение.** Число вроде `54.13` констатирует факт, но не причину. Ревьюеры и будущие сопровождающие не могут понять, является ли это намеренным ожиданием, границей или случайностью. Тест перестаёт быть исполняемой документацией.
* **Сопровождаемость.** Когда меняется правило (ставка налога, тайм-аут, размер страницы), вам приходится разыскивать каждую копию литерала и понимать, какая `7` означала «дни», а какая — «максимум попыток». Безымянное дублирование делает безопасные правки дорогими и подверженными ошибкам.
* **Надёжность / ложная уверенность.** Если ожидаемое значение неверно — или верно лишь по совпадению — ничто в тесте этого не выявит. Хуже того, авторы часто «чинят» тест с магическим числом, пересчитывая ожидаемое значение по _боевой_ формуле (`expect(total).toBe(subtotal * (1 + rate)))`), превращая проверку в тавтологию, которая переписывает тестируемый код и никогда не может упасть по правильной причине.
* **Диагностика.** Когда такой тест ломается, сообщение об ошибке — это всего лишь «ожидалось 54.13, получено 54.12» без подсказки о том, какой вход или правило дали это число, что замедляет отладку.

## Treatment

Примените **замену магического числа символьной константой** (Replace Magic Number with Symbolic Constant, Месарош): дайте каждому значимому значению имя, объясняющее его роль, и сделайте связь между входами и ожидаемым результатом явной.

Конкретные шаги:

1. **Назовите входные данные.** Вынесите литералы, используемые как тестовые данные, в локальные константы с понятными именами или в вызовы строителя фикстур (`const SUBTOTAL = 50.00`, `const TAX_RATE_PCT = 8.25`). Используйте «Мать объектов» (Object Mother) / строитель для объектных фикстур, чтобы были видны только те значения, которые важны для теста.
2. **Назовите и поясните ожидаемое значение.** Оставьте ожидаемый результат _независимым_ литералом, но дайте ему имя и задокументируйте, как он был выведен (`const EXPECTED_TOTAL = 54.13; // 50.00 + налог 8.25%`). **Не** пересчитывайте его по боевой формуле — это лишь перепроверяет код против самого себя.
3. **Свяжите входные данные с проверкой**, чтобы читатель мог проверить арифметику на глаз, либо сверяйтесь с производным, но независимым эталонным значением.
4. **Оставьте по-настоящему самоочевидные значения как есть.** `0`, `1`, `-1`, индексы массивов и очевидные количества (`items).toHaveLength(2)`) обычно не нуждаются в именах; приберегите константы для значений, смысл которых не самоочевиден. Выносите общую константу в единый источник истины только тогда, когда это действительно одно и то же понятие везде.

```js
// До
test('checkout works', () => {
  const total = checkout(cartFor(50), 8.25);
  expect(total).toBe(54.13);
});

// После
const SUBTOTAL = 50.00;
const TAX_RATE_PCT = 8.25;
const EXPECTED_TOTAL = 54.13; // SUBTOTAL плюс налог с продаж 8.25%

test('applies sales tax to the subtotal', () => {
  const total = checkout(cartFor(SUBTOTAL), TAX_RATE_PCT);
  expect(total).toBe(EXPECTED_TOTAL);
});

```

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

## Detected by

- **eslint** `no-magic-numbers` — Магические числа следует объявлять как именованные константы (https://eslint.org/docs/latest/rules/no-magic-numbers)
- **typescript-eslint** `@typescript-eslint/no-magic-numbers` — Запретить магические числа (TypeScript) (https://typescript-eslint.io/rules/no-magic-numbers/)
- **sonar** `javascript:S109` — Магические числа не должны использоваться (https://rules.sonarsource.com/javascript/RSPEC-109/)
- **checkstyle** `MagicNumber` — MagicNumber (https://checkstyle.sourceforge.io/checks/coding/magicnumber.html)
- **tsDetect** `Magic Number Test` — Magic Number Test (детектор, специфичный для тестов) (https://testsmells.org/pages/testsmells.html)
