Чистый код в тестах

Чистота тестового кода в Protractor

Сложные e2e-сценарии часто страдают избыточностью и непредсказуемостью. В контексте Protractor это проявляется в чрезмерном количестве ожиданий, дублировании селекторов, неконтролируемой логике внутри it-блоков и слабой модульности. Поддержка таких тестов становится дорогой и рискованной, особенно при частых изменениях UI.

Тесты в Protractor должны быть декларативными. Логические ветвления, вычисления и вспомогательные фильтры желательно переносить в утилиты или Page Object. Внутри it остаются только действия и проверки. Это делает сценарий линейным и удобным для понимания.

Плохо

it('добавляет товар в корзину', () => {
  element(by.css('.product')).click();
  browser.sleep(3000);
  const price = parseFloat(
    element(by.css('.price')).getText().then(t => t.replace('₽', ''))
  );
  if (price > 1000) {
    element(by.css('.discount')).click();
  }
  expect(element(by.css('.cart-item')).isPresent()).toBe(true);
});

Хорошо

it('добавляет товар в корзину', () => {
  catalogPage.addItem();
  cartPage.applyPossibleDiscount();
  expect(cartPage.hasItem()).toBe(true);
});

Page Object как основа структуры

Page Object уменьшает связность между тестом и DOM-структурой. Все селекторы, ожидания и операции концентрируются в одном месте. Это уменьшает количество изменений в тестах при рефакторинге интерфейса.

Ключевые моменты Page Object

  • отсутствие асинхронной логики в тестах;
  • единая точка обновления селекторов;
  • возможность переиспользования шагов;
  • повышение читаемости сценариев.

Важно избегать чрезмерной универсальности объектов страницы. Page Object описывает конкретный экран, а не абстрактный модуль. Дробление экранов на мелкие объекты оправдано только при действительно независимых зонах UI.

Чёткие ожидания вместо sleep

browser.sleep() нарушает предсказуемость и увеличивает время прогона. Вместо фиксированных задержек применяются ExpectedConditions или кастомные ожидания, привязанные к состоянию UI.

const EC = protractor.ExpectedConditions;
browser.wait(EC.visibilityOf(element), 5000);

Ожидания выражают намерение: что должно произойти для продолжения сценария. Это повышает стабильность тестового набора и снижает флейки.

Единый стиль работы с селекторами

Смешение by.css, by.id, by.binding и by.model без чётких правил усложняет навигацию по коду. Чистый тестовый код определяет последовательность предпочтений: обычно CSS-селекторы первичны, а Angular-специфичные — вспомогательны. Полезна политика именования CSS-классов для тестирования, например data-role или data-testid, что устраняет хрупкость селекторов при переработке стилей.

Отказ от дублирования шагов

Дублирование шагов увеличивает стоимость изменений и приводит к несогласованности поведения. В Protractor это часто проявляется в однотипных цепочках click(), sendKeys() и wait(). Инкапсуляция в Page Object или утилиты позволяет переиспользовать логику и формировать тестовые DSL-конструкции.

loginPage.loginAs('admin');
orderPage.submitOrder();

Такие конструкции повышают выразительность и уменьшают когнитивную нагрузку.

Работа с тестовыми данными

Чистый код в тестах предусматривает чёткую границу между сценарием и фиксированными данными. Строки, даты и константы выносятся в структуру фикстур или фабрик. Динамические данные генерируются детерминированно, чтобы исключить случайные падения.

Допустимы три источника данных:

  • статические фикстуры;
  • фабрики для генерации валидных сущностей;
  • данные, возвращаемые API или базе, но контролируемые тестом.

Смешение этих уровней внутри it нарушает читаемость и усложняет отладку.

Семантика проверок

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

expect(orderPage.status()).toEqual('Оформлен');

Чрезмерное количество проверок в одном тесте делает его хрупким. Один it выражает один сценарий и одну точку достоверности.

Линейность сценариев

Тест должен читаться сверху вниз без необходимости удерживать сложное состояние в голове. Protractor допускает цепочки асинхронных операций, что при неправильном применении приводит к вложенности. Линейность достигается Page Object-методами и грамотным использованием await в современных версиях, полностью убирающим callback-ад.

Нарезка тестов и независимость окружения

Тесты должны быть изолированы. Результат одного сценария не должен становиться предусловием другого. В Protractor это приводит к необходимости сброса состояния, использованию API для подготовки данных или старту с начального экрана. Чистота достигается при минимальном количестве глобальных зависимостей.

Консистентность ошибок

Важен единый подход к диагностике падений. Page Object может содержать сообщения об ошибках или логирование, упрощающее поиск причины. Разрозненные сообщения затрудняют анализ и приводят к неверным выводам о стабильности тестового набора.

Эволюция тестового набора

Чистота тестового кода — процесс, а не одноразовое действие. Избавление от sleep, сокращение дублирования, стандартизация селекторов и рост выразительности сценариев ведут к тестам, которые легче сопровождать и масштабировать. В контексте Protractor это особенно важно, учитывая тесную связь с UI-слоем и насыщенность Angular-специфичными механизмами.

Такая дисциплина позволяет техническому долгу не накапливаться, а тестовой базе — оставаться инструментом уверенности, а не источником хаоса.