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

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

## Signs and Symptoms

Вы распознаёте этот запах, когда тесты выглядят написанными копипастом, а не переиспользованием:

* **Одна и та же конструкция фикстуры/объекта** дословно пересобирается в начале теста за тестом.
* **Идентичные последовательности проверок** (одни и те же 3–4 вызова `expect` в том же порядке) повторяются между тестами.
* Новые тесты очевидно склонированы из старого с подправленной одной строкой.
* **Одни и те же магические литералы** (ID, URL-адреса, даты, строки ошибок) встречаются снова и снова.
* Изменение одного конструктора или сигнатуры API ломает **десятки тестов разом** (хирургия дробью, shotgun surgery).
* Вы видите **почти дублированные тесты, отличающиеся только входными/ожидаемыми значениями**, — явный кандидат на параметризованный/табличный тест.

```js
test('flight can be cancelled', () => {
  const airport = new Airport('YYC', 'Calgary');                              // дублируется
  const flight  = new Flight('AC123', airport, new Date('2026-06-01T10:00')); // дублируется
  flight.cancel();
  expect(flight.status).toBe('CANCELLED');
});

test('flight can be delayed', () => {
  const airport = new Airport('YYC', 'Calgary');                              // копипаст
  const flight  = new Flight('AC123', airport, new Date('2026-06-01T10:00')); // копипаст
  flight.delay(30);
  expect(flight.status).toBe('DELAYED');
});

```

## Reasons for the Problem

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

* Копирование предыдущего теста — самый быстрый способ написать следующий.
* К тестам относятся как к «гражданам второго сорта» — их не рефакторят и не держат к тому же стандарту DRY, что и боевой код.
* Нет общих фикстур, методов создания (Creation Methods) или строителей тестовых данных, а авторы незнакомы с параметризованными/табличными тестами.

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

* **Сопровождаемость:** Месарош напрямую связывает этот запах с _хрупким тестом_ (Fragile Test) — когда «одни и те же последовательности кода появляются много раз во многих тестах», одно изменение в боевом коде означает правку одного и того же в N местах. Стоимость сопровождения растёт с числом копий, а не с числом различных поведений.
* **Читаемость:** повторяющийся шаблонный код хоронит ту единственную строку, которая на самом деле делает каждый тест особенным, поэтому читатели не могут быстро увидеть, что именно проверяется.
* **Надёжность:** копипаст провоцирует ошибки копипаста, а исправления применяются к одной копии, но не к её собратьям, оставляя несогласованные, противоречивые тесты.
* **Ложная уверенность:** ошибочная проверка, которую размножили, теперь неверна сразу во многих местах, а клонированные тесты молча расходятся, пока больше не проверяют того, что заявляют их имена.

**Оговорка — DRY против DAMP:** тесты также ценят свойство быть _описательными и осмысленными фразами_ (Descriptive And Meaningful Phrases). Не абстрагируйте чрезмерно до того, что читателю придётся гоняться за хелперами, чтобы понять тест. Выносите подлинное, раскрывающее намерение дублирование; сохраняйте существенную, специфичную для каждого теста деталь видимой и локальной.

## Treatment

Устраните _случайное_ дублирование, сохраняя суть каждого теста очевидной:

1. **Вынесите тестовые утилиты / методы создания** (Object Mother, строитель тестовых данных) для повторяющейся конструкции объектов, чтобы каждый тест называл только важные для него значения.
2. **Используйте `beforeEach` / неявную подготовку (Implicit Setup)** для контекста, который действительно общий и релевантен каждому тесту в блоке, — но избегайте сокрытия состояния, от которого зависит тест (это меняет дублирование на _непрозрачный тест_ (Obscure Test)).
3. **Вынесите пользовательские проверки / хелперы верификации** для повторяющихся многошаговых последовательностей проверок, идеально проверяющих одно логическое условие.
4. **Сверните почти идентичные тесты в параметризованные / табличные тесты** (`it.each` / `test.each`), чтобы пары вход + ожидаемый вывод жили в одной таблице.
5. **Замените дублированные магические литералы** именованными константами или значениями по умолчанию строителя.

До — дублированная конструкция и три почти идентичных теста:

```js
test('rejects negative amount', () => {
  expect(() => validateAmount(-1)).toThrow(RangeError);
});
test('rejects zero amount', () => {
  expect(() => validateAmount(0)).toThrow(RangeError);
});
test('rejects NaN amount', () => {
  expect(() => validateAmount(NaN)).toThrow(RangeError);
});

```

После — метод создания убирает дублирование подготовки, а таблица заменяет клоны:

```js
// общий метод создания: тесты указывают только значимые переопределения
const aFlight = (overrides = {}) =>
  new Flight('AC123', new Airport('YYC', 'Calgary'),
             new Date('2026-06-01T10:00'), overrides);

it.each([-1, 0, NaN])('rejects invalid amount %p', (amount) => {
  expect(() => validateAmount(amount)).toThrow(RangeError);
});

```

Прогоните детектор копипаста (ниже) по вашим тестовым исходникам, затем рефакторите сначала самые крупные/наиболее повторяющиеся блоки.

## Detected by

- **eslint-sonarjs** `no-identical-functions` — Функции не должны иметь идентичных реализаций (https://github.com/SonarSource/eslint-plugin-sonarjs/blob/master/docs/rules/no-identical-functions.md)
- **eslint-sonarjs** `no-duplicate-string` — Строковые литералы не должны дублироваться (https://github.com/SonarSource/eslint-plugin-sonarjs/blob/master/docs/rules/no-duplicate-string.md)
- **sonar** `javascript:S4144` — Функции не должны иметь идентичных реализаций (https://rules.sonarsource.com/javascript/RSPEC-4144/)
- **pmd** `cpd` — Детектор копипаста (CPD) — дублированные блоки кода (https://pmd.github.io/pmd/pmd_userdocs_cpd.html)
