---
title: "Тестирование деталей реализации"
type: "test-smell"
slug: "testing-implementation-details"
url: "http://localhost:3000/ru/test-smells/testing-implementation-details.md"
category: "Запахи мокирования"
description: "Тест проверяет то, как код работает изнутри — приватные поля, внутренние вызовы методов, структуру DOM или CSS-классы — вместо наблюдаемого поведения, от которого зависит реальный потребитель."
---
# Тестирование деталей реализации

> Тест проверяет то, как код работает изнутри — приватные поля, внутренние вызовы методов, структуру DOM или CSS-классы — вместо наблюдаемого поведения, от которого зависит реальный потребитель.

## Signs and Symptoms

Вы распознаёте этот запах, когда тест дотягивается _за_ публичный контракт и фиксирует механику за ним. Частые признаки:

* Проверки **приватного/внутреннего состояния** или полей либо прямой вызов приватных методов (часто через приведения типов, рефлексию или `// @ts-expect-error`).
* Слежка за тем, что **внутренний хелпер был вызван**, или проверка этого (`expect(internalCalc).toHaveBeenCalled()`) вместо проверки результата.
* UI-тесты, ищущие по **CSS-классу, тегу, хукам `data-*` или позиции в DOM**, а не по роли/метке/тексту — например, `container.querySelector('.btn-primary > span:nth-child(2)')`, `wrapper.state()`, `wrapper.find('SomeChildComponent').props()`.
* Снапшот-тесты по **целым деревьям рендера / сериализованным внутренним объектам**, так что любая правка разметки приводит к падению.
* Тесты, которые **ломаются при каждом рефакторинге**, хотя функция всё ещё работает (ложные отрицания), и наоборот продолжают проходить после логической ошибки, потому что лишь перепроверяют обвязку (ложные срабатывания).

```js
// ЗАПАХ: связывает тест с внутренностями компонента и структурой DOM
test('counter increments', () => {
  const wrapper = mount(<Counter />);
  wrapper.instance().handleClick();          // напрямую вызывает приватный метод
  expect(wrapper.state('count')).toBe(1);    // проверяет внутреннее состояние
  expect(wrapper.find('.count-display').text()).toBe('1'); // хрупкий CSS-селектор
});

```

Та же форма проявляется и на стороне сервера: `expect(service._cache.size).toBe(1)` или проверка точной последовательности внутренних вызовов, которые делает метод.

## Reasons for the Problem

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

* До внутреннего состояния _легко дотянуться_ — публичное поле, экспортируемый хелпер или `container.querySelector` прямо под рукой, тогда как прогон реального поведения требует больше подготовки.
* Погоня за метриками покрытия: тестирование каждого приватного метода один-к-одному кажется тщательным.
* Обильное использование моков толкает людей к проверке «был ли вызван этот коллаборатор» вместо «произошло ли нужное».
* Инструменты, которые это поощряют: поверхностный рендеринг (shallow rendering) / API `instance()` / `state()` или захват узлов по имени класса.

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

* **Сопровождаемость / хрупкость.** Это _хрупкий тест_ (Fragile Test) Месароша, вызванный _переспецифицированным ПО_ (Overspecified Software): тест фиксирует поведение, которого потребитель никогда не требовал, поэтому безобидные рефакторинги (переименование метода, реструктуризация разметки, изменение приватного поля) ломают зелёные тесты без реальной причины. Тесты становятся налогом на рефакторинг, а не страховочной сеткой.
* **Ложная уверенность (главная опасность, по Кенту Ч. Доддсу).** Тесты деталей реализации ошибаются в _обе_ неверные стороны: **ложные отрицания** (тест краснеет, хотя функция всё ещё работает) и **ложные срабатывания** (тест остаётся зелёным, хотя функция сломана — вы проверили обвязку, а не результат). В любом случае набор перестаёт говорить вам правду.
* **Читаемость.** Тест документирует, _как_ код устроен, а не _что он гарантирует_. Читатель не может понять, какое поведение действительно важно, и тест больше не служит примером использования или спецификацией.
* **Связанность.** Он замораживает текущие решения о дизайне, отбивая охоту именно к тем рефакторингам, которые тесты должны делать безопасными.

## Treatment

Тестируйте через **публичный контракт** — ту же поверхность, которой касается реальный вызывающий или пользователь, — и проверяйте **наблюдаемый вывод**: возвращаемые значения, выброшенные ошибки, испущенные события, сохранённое состояние или отрисованный/видимый UI.

1. **Определите потребителя.** Для модуля это его экспортируемый API; для UI-компонента это пользователь (клики, ввод) и то, что он может видеть.
2. **Подавайте входные данные так, как это делал бы потребитель**, а не вызовом приватных методов. Инициируйте реальный клик вместо вызова обработчика; вызывайте публичный метод вместо хелпера.
3. **Проверяйте результаты, а не внутренности.** Замените проверки `state()`/приватного поля/`toHaveBeenCalled` проверками того, что выходит наружу.
4. **Ищите элементы UI по доступности, а не по структуре** — по роли, метке, тексту — а не по CSS-классам, тегам или `nth-child`.
5. **Перестаньте тестировать приватные методы напрямую.** Покрывайте их через публичный метод, который их использует; если приватная единица достаточно сложна, чтобы нуждаться в собственных тестах, это сигнал вынести её в отдельный модуль с собственным публичным API.
6. **Приберегите проверки моков/шпионов для настоящих границ** (сеть, время, платёжный шлюз), где _сам вызов_ и есть наблюдаемое поведение, — а не для внутренних коллабораторов.

```js
// ДО: тестирует детали реализации
const wrapper = mount(<Counter />);
wrapper.instance().handleClick();
expect(wrapper.state('count')).toBe(1);
expect(wrapper.find('.count-display').text()).toBe('1');

// ПОСЛЕ: тестирует наблюдаемое поведение через публичный, обращённый к пользователю контракт
render(<Counter />);
await userEvent.click(screen.getByRole('button', { name: /increment/i }));
expect(screen.getByText('1')).toBeInTheDocument();

```

Эмпирическое правило: если рефакторинг, сохраняющий поведение, ломает тест, значит тест проверял деталь реализации.

## Detected by

- **eslint-testing-library** `testing-library/no-node-access` — no-node-access (https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-node-access.md)
- **eslint-testing-library** `testing-library/no-container` — no-container (https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-container.md)
