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

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

## Signs and Symptoms

Вы видите, как **одна и та же проверка встречается дважды** (тот же матчер, те же аргументы) в одном тесте, либо несколько скопированных блоков проверок, которые отличаются лишь литеральными значениями и заново проверяют _одно и то же_ поведение. Характерные признаки:

* Буквально идентичная строка проверки встречается два и более раз в теле теста.
* Множество вызовов `expect(...)`/`assertEquals(...)`, проверяющих одно и то же условие с разными входными данными, втиснуты в один метод, имя которого описывает лишь один сценарий.
* Оставшиеся от отладки проверки ("дай-ка я ещё проверю X"), которые так и не убрали.
* Имя теста (например, `testXmlSanitizer`) не даёт никакой подсказки, какая из множества его проверок упала.

```js
test('sanitizer accepts valid input', () => {
  expect(isValid('plain text')).toBe(true);
  expect(isValid('with spaces')).toBe(true);
  expect(isValid('Fritz-box')).toBe(true);   // "минус допустим"
  expect(isValid('Fritz-box')).toBe(true);   // <-- точный дубликат, ничего не добавляет
  expect(isValid('<script>')).toBe(false);
});

```

Продублированная строка `Fritz-box` — это канонический Duplicate Assert; более широкий запах в том, что один метод незаметно собирает множество проверок одного и того же условия под одним именем.

## Reasons for the Problem

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

* **Копипаст.** Блок проверки копируется, и в нём меняются литералы (а иногда не меняется вообще ничего).
* **Остатки отладки.** Дополнительные проверки, добавленные для исследования поведения, забывают убрать.
* **Группировка.** Разработчики тестируют "один метод", сваливая все случаи в один тест вместо того чтобы разделить их, и получают повторяющиеся, почти идентичные проверки.

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

* **Ложная уверенность.** По-настоящему идентичная, продублированная проверка добавляет _ноль_ покрытия — она никогда не упадёт, если проходит её двойник, — зато создаёт впечатление, что тест тщательнее, чем он есть.
* **Трудная диагностика падений.** Когда несколько однотипных проверок собраны в одном методе, отчёт о падении указывает на метод, а не на конкретные входные данные, которые сломались. Хуже того, при стандартной конфигурации тест останавливается на первой упавшей проверке, поэтому последующие продублированные проверки не выполняются вовсе — вы чините одну, перезапускаете, натыкаетесь на следующую (пересекается с _Assertion Roulette_).
* **Плохая читаемость.** Читатель не может понять, намеренно ли это повторение (разные случаи) или ошибка (настоящий дубликат), а имя теста документирует лишь одно из множества условий.
* **Стоимость сопровождения.** Измените тестируемое поведение — и вам придётся выискивать и обновлять каждую продублированную проверку; пропустите одну — и набор тестов станет противоречивым.

## Treatment

1. **Удалите точные дубликаты.** Если проверка байт в байт совпадает с другой в том же тесте, уберите её — это балласт, а не покрытие.
2. **Параметризуйте эквивалентные случаи.** Когда "дубликаты" на самом деле представляют _одно и то же_ условие с _разными_ входными данными, преобразуйте их в табличный/параметризованный тест (`test.each` в Jest/Vitest, `@ParameterizedTest` в JUnit 5). Каждая строка отображается в отчёте и именуется отдельно, поэтому падения точно указывают на проблемный вход.
3. **Разнесите по-настоящему разные условия** по отдельным тестам, каждый с именем, которое сообщает, что именно он проверяет (`принимает имена хостов с дефисами`, `отклоняет теги script`).
4. **Именуйте по намерению.** Имя теста должно описывать одно поведение; если это не получается — это сигнал к разделению.

```js
// До — продублированные / собранные вместе проверки в одном непрозрачном тесте
test('isValid', () => {
  expect(isValid('plain text')).toBe(true);
  expect(isValid('with spaces')).toBe(true);
  expect(isValid('Fritz-box')).toBe(true);
  expect(isValid('Fritz-box')).toBe(true); // дубликат
  expect(isValid('<script>')).toBe(false);
});

// После — одна проверка, каждый случай назван и отображается в отчёте отдельно
test.each([
  ['plain text', true],
  ['with spaces', true],
  ['Fritz-box',  true],   // минус допустим
  ['<script>',   false],  // отклоняет разметку
])('isValid(%j) === %s', (input, expected) => {
  expect(isValid(input)).toBe(expected);
});

```

Дублирующая строка удалена, разные входы явны, а падение называет точный случай.
