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

> Невнятный тест — это тест, в котором читатель по одному только методу теста не может понять, какой сценарий настроен, какое поведение проверяется и какой результат ожидается, потому что намерение погребено под избытком деталей, недостатком контекста или логикой, спрятанной где-то ещё.

## Signs and Symptoms

Вы читаете тест и всё равно не можете ответить на три вопроса: _какова настройка, что именно проверяется и каков ожидаемый результат?_ Цепочка причина/следствие скрыта. Месарос группирует причины на «слишком много информации» и «слишком мало информации». Типичные приметы:

* **Eager Test (жадный тест)** — один метод теста проверяет много несвязанных вариантов поведения, поэтому никакое единое намерение не ясно.
* **Mystery Guest (таинственный гость)** — фикстура или ожидаемые значения живут вне теста (файл, общая запись в БД, фикстура, загружаемая по имени), поэтому вы не видите, почему проверка должна пройти.
* **General Fixture (общая фикстура)** — большой общий `beforeEach`/фабрика строит куда больше, чем нужно этому тесту; немногие значимые поля тонут в шуме.
* **Irrelevant Information (несущественная информация)** — страницы данных настройки или гигантский снимок, где на деле важны лишь одно-два поля.
* **Hard-Coded Test Data (захардкоженные тестовые данные)** — «магические» литералы (`42`, `"a3f9-..."`, `userId=7`) без именованного смысла, повторяющиеся в настройке и в проверках.
* **Indirect Testing (косвенное тестирование)** — тест тыкает в проверяемую систему через несколько других объектов, затемняя то, что на самом деле проверяется.

```js
// Невнятно: Eager + Mystery Guest + Irrelevant Information
test('user', async () => {
  const data = loadFixture('users.json');        // таинственный гость: значения живут в файле
  const svc = new UserService(data, cfg, clock); // общая фикстура: большая часть не используется
  const u = await svc.create({ ...data[0], roles: ['a','b'], flags: 0x1f });

  expect(u.id).toBeDefined();
  expect(u.email).toContain('@');
  expect(await svc.count()).toBe(data.length + 1); // почему это число? ответ — в файле
  expect(await svc.login(u.email, 'p@ss')).toBe(true); // несвязанное второе поведение
});

```

Здесь невозможно понять, что доказывает тест, не открыв `users.json`, и он на деле проверяет создание _и_ вход за один раз.

## Reasons for the Problem

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

* Тесты разрастаются наслоением: разработчик добавляет «ещё одну проверку» в существующий тест вместо того, чтобы написать новый, порождая Eager Test (жадный тест).
* Совместное использование настройки кажется эффективным, поэтому единая широкая фикстура или `beforeEach` переиспользуется повсюду (General Fixture, общая фикстура), а внешние файлы/наполнение БД загружаются по имени (Mystery Guest, таинственный гость).
* Копирование правдоподобно выглядящих данных тащит за собой десятки несущественных полей и «магических чисел».
* Чрезмерное использование моков или проход через множество сотрудников-зависимостей превращает модульный тест в Indirect Testing (косвенное тестирование).

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

* **Читаемость:** тест — это документация задуманного поведения. Если читатель не может восстановить цепочку «Настройка → Действие → Проверка», эта документационная ценность теряется.
* **Сопровождаемость:** когда невнятный тест падает, вы не знаете, какое поведение сломалось и ошибка в тесте или в коде, поэтому изменения медленны и рискованны.
* **Надёжность:** Mystery Guest и General Fixture связывают тест с внешним/общим состоянием, вызывая нестабильные или зависящие от порядка падения (и нарушая свойство «свежий, детерминированный» хорошего модульного теста).
* **Ложная уверенность:** Eager Test маскирует покрытие — падение в начале метода обрывает выполнение последующих проверок, поэтому поведение, которое вы _думаете_, что протестировано, может никогда не выполниться. И, как отмечает Месарос, ошибки в коде проще спрятать в невнятном тесте, порождая Buggy Tests (баговые тесты), которые проходят по неверным причинам.

## Treatment

Сделайте так, чтобы каждый тест рассказывал самодостаточную историю, намерение которой видно в теле теста.

1. **Проверяйте одно условие на тест.** Разбейте Eager Test на сфокусированные тесты, каждый названный по поведению, которое он проверяет. Это также устраняет маскировку покрытия.
2. **Встройте нужную фикстуру (уберите Mystery Guest).** Постройте данные, от которых зависит тест, _в самом тесте_ или через явный, именованный метод создания/строитель — не загружайте анонимные файлы или общие строки БД.
3. **Используйте минимальную / свежую фикстуру.** Стройте только то, что нужно этому тесту; замените широкий общий `beforeEach` строителем, который задаёт значения по умолчанию для шума и позволяет каждому тесту задать лишь проверяемое поле.
4. **Именуйте свои данные.** Замените «магические» литералы раскрывающими намерение константами/переменными, чтобы проверка объясняла себя сама.
5. **Проверяйте смысл, а не всё подряд.** Предпочитайте прицельные проверки гигантскому снимку; если делаете снимок, держите его маленьким и пригодным к ревью, чтобы значимые факты не утонули в несущественном выводе.
6. **Скрывайте механику, а не намерение.** Уберите побочную обвязку в хорошо названные вспомогательные/утилитарные методы теста, чтобы тело теста читалось как настройка → действие → ожидание.

```js
// До (невнятно)
test('user', async () => {
  const data = loadFixture('users.json');
  const svc = new UserService(data, cfg, clock);
  const u = await svc.create({ ...data[0], roles: ['a','b'], flags: 0x1f });
  expect(await svc.count()).toBe(data.length + 1);
});

// После: одно намерение, фикстура встроена, данные именованы
test('create() persists a new user', async () => {
  const svc = userServiceWith([]);                 // минимальная, свежая фикстура
  const newUser = aUser({ email: 'ada@example.com' }); // строитель задаёт значения шума по умолчанию

  const created = await svc.create(newUser);

  expect(created.email).toBe('ada@example.com');
  expect(await svc.count()).toBe(1);               // самоочевидно, без внешнего файла
});

```

Стремитесь к форме AAA (Arrange-Act-Assert, Подготовка-Действие-Проверка): читатель должен ухватить сценарий, не покидая метода теста.

## Detected by

- **eslint-plugin-jest** `max-expects` — jest/max-expects (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/max-expects.md)
- **eslint-plugin-vitest** `max-expects` — vitest/max-expects (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/max-expects.md)
- **eslint-plugin-jest** `no-large-snapshots` — jest/no-large-snapshots (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-large-snapshots.md)
- **eslint-plugin-vitest** `no-large-snapshots` — vitest/no-large-snapshots (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-large-snapshots.md)
- **eslint-plugin-jest** `max-nested-describe` — jest/max-nested-describe (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/max-nested-describe.md)
