Test Smells.
Recurring problems in test code — how to recognize each one, why it hurts, how to fix it, and the lint rules that detect it.
Тест упаковывает множество недокументированных проверок в один метод, так что при падении нельзя понять, какая проверка сработала и почему — приходится гадать.
Нетерпеливый тест проверяет несколько разных методов или поведений тестируемого модуля в одном тестовом методе вместо того, чтобы сосредоточиться на одном поведении.
Избыточная проверка сравнивает значение с самим собой или с литералом, равным ему по построению, поэтому её исход предопределён, и она в принципе не может ни упасть, ни обнаружить регрессию.
Тестовый метод, который выполняет код, но не содержит ни одной проверки, поэтому проходит, пока ничего не выбрасывает исключение, — а его подлинное назначение и то, что он проверяет, остаются неизвестными.
Один тестовый метод проверяет одно и то же условие несколько раз — повторяя идентичное утверждение или заново проверяя эквивалентную логику — вместо того чтобы удалить избыточную проверку или разнести разные случаи по отдельным сфокусированным тестам.
Тест проверяет поведение, сравнивая строковое представление объекта (toString(), JSON.stringify(), отрендеренный HTML) с ожидаемым строковым литералом, тем самым привязываясь к несущественным деталям форматирования вместо действительно важных значений.
Общая подготовка строит одну большую фикстуру «на все случаи жизни», покрывающую потребности всех тестов, но каждый отдельный тест задействует лишь небольшую её часть.
Тест, чьи входные данные или ожидаемые результаты находятся во внешнем ресурсе — файле, сидовых данных БД или общей фикстуре, — поэтому понять такой тест и доверять ему, читая только его, невозможно.
Ресурсный оптимизм — это когда тест предполагает, что внешний ресурс (файл, каталог, таблица БД, переменная окружения или сетевой эндпоинт) уже существует и находится в известном состоянии, вместо того чтобы создать и проверить его, из-за чего тест проходит или падает недетерминированно.
Тестовый класс инициализирует поля фикстуры в конструкторе вместо специального хука подготовки фреймворка (setUp / @BeforeEach / TestInitialize), обходя жизненный цикл теста.
Невнятный тест — это тест, в котором читатель по одному только методу теста не может понять, какой сценарий настроен, какое поведение проверяется и какой результат ожидается, потому что намерение погребено под избытком деталей, недостатком контекста или логикой, спрятанной где-то ещё.
Тест, который использует `if`/`switch`/тернарные операторы, циклы или `try`/`catch`, чтобы решать, что выполнять или проверять, так что его поведение — и проверяет ли он что-либо вообще — зависит от того, какая ветка выполнится во время работы.
Дублирование тестового кода — это когда один и тот же код подготовки, действия или проверки копируется в множество тестов, так что одно изменение вынуждает к правкам во многих местах, а тесты деградируют в хрупкие, почти идентичные копии.
Тест жёстко прописывает необъяснённые числовые литералы во входных данных и проверках, скрывая, что эти числа означают и откуда они взялись.
Тест, который использует гораздо больше кода, чем нужно для изложения своего сценария, погребая ту единственную связку «причина-следствие», которую он проверяет, под шаблонной подготовкой, нерелевантными данными и проверками поле-за-полем.
Неустойчивый (flaky) тест на неизменном коде то проходит, то падает, потому что его исход зависит от тайминга, порядка выполнения, общего состояния или иных недетерминированных факторов, а не от проверяемого поведения.
Спящий тест приостанавливает выполнение захардкоженной задержкой (`Thread.sleep`, `setTimeout`, `cy.wait(2000)`, `page.waitForTimeout`), чтобы дождаться асинхронной работы, вместо того чтобы ждать само нужное условие.
Война тестовых прогонов — это когда тесты проходят у одного человека, но начинают случайно падать, стоит нескольким людям или CI-задачам запустить набор тестов одновременно, потому что тесты конфликтуют на общей персистентной фикстуре.
Тест проходит или падает в зависимости от того, какие другие тесты выполнялись до него, потому что тесты «протекают» и полагаются на общее изменяемое состояние вместо того, чтобы каждый сам создавал и убирал свою фикстуру.
Тест настраивает столько мок-объектов и заглушенных взаимодействий, что настройка моков затмевает саму проверку, поэтому тест в итоге испытывает моки, а не реальное поведение.
Тест проверяет то, как код работает изнутри — приватные поля, внутренние вызовы методов, структуру DOM или CSS-классы — вместо наблюдаемого поведения, от которого зависит реальный потребитель.
Переспецифицированный тест проверяет гораздо больше, чем требует проверяемое поведение — фиксирует точные строки вывода, полную форму объектов, порядок элементов коллекции или каждый вызов внутреннего сотрудника-зависимости, — поэтому он ломается всякий раз, когда меняется не связанная с ним деталь реализации.
Пустой тест — это тестовый метод, тело которого не содержит ни одной исполняемой инструкции, поэтому он всегда проходит, ничего при этом не проверяя.
Тест, который закоммичен в кодовую базу, но никогда не выполняется, потому что он пропущен, отключён или закомментирован, создавая видимость покрытия, ничего на самом деле не проверяя.
Продакшен-код содержит логику, ветви или члены, существующие лишь для поддержки тестирования, что размывает грань между тем, что попадает в продакшен, и тем, что просто тестируется.
Боевой код содержит методы, аксессоры состояния или швы, которые существуют только для использования тестами, загрязняя реальный API и провоцируя тесты, проверяющие внутренности вместо поведения.
Продакшен-код, чей дизайн (тесная связанность, скрытые зависимости, глобальное состояние, недетерминированный ввод-вывод или сугубо асинхронные интерфейсы) заставляет тесты идти на неуклюжие ухищрения или вовсе делает невозможным проверку модуля в изоляции.