惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
GbyAI
GbyAI
MongoDB | Blog
MongoDB | Blog
人人都是产品经理
人人都是产品经理
A
About on SuperTechFans
Microsoft Security Blog
Microsoft Security Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
量子位
腾讯CDC
Google DeepMind News
Google DeepMind News
Vercel News
Vercel News
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
博客园 - Franky
M
MIT News - Artificial intelligence
C
CERT Recently Published Vulnerability Notes
B
Blog RSS Feed
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Simon Willison's Weblog
Simon Willison's Weblog
Attack and Defense Labs
Attack and Defense Labs
L
Lohrmann on Cybersecurity
S
Schneier on Security
MyScale Blog
MyScale Blog
The Last Watchdog
The Last Watchdog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
PCI Perspectives
PCI Perspectives
博客园 - 聂微东
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Troy Hunt's Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
K
Kaspersky official blog
F
Fortinet All Blogs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
D
Docker
Security Latest
Security Latest
P
Privacy & Cybersecurity Law Blog
T
Tenable Blog
B
Blog
有赞技术团队
有赞技术团队
TaoSecurity Blog
TaoSecurity Blog
C
Check Point Blog
Latest news
Latest news
H
Hackread – Cybersecurity News, Data Breaches, AI and More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Help Net Security
Help Net Security
D
DataBreaches.Net
Google DeepMind News
Google DeepMind News
N
News and Events Feed by Topic
J
Java Code Geeks

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Реанимация легаси: как мы заставили древний SEO-сервис говорить на другом языке
dmitrofanova · 2026-05-04 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение12 мин

Охват и читатели2.7K

Кейс

У каждого второго разработчика или QA есть сервис, который:

  • Написан на древней версии языка

  • Не имеет авторов

  • Тесты не работают

  • Документация — одна страница

  • Но он стабильно работает, и его все боятся трогать

А потом прилетает задача: добавить мультиязычность, или новый тип данных, или интеграцию с внешним API. И вы понимаете: либо вы его трогаете сейчас, либо он ломается сам через полгода в самый неподходящий момент.

Всем привет! На связи Даша, QA команды «Платформа Web» в Иви, и Андрей, наш разработчик. Нам достался Pyro — SEO-сервис с минимумом тестов, документации и авторов. Задача: добавить мультиязычность, ничего не сломать. Рассказываем, как мы чистили мусор, писали скрипты перевода и восстанавливали пирамиду тестов. Спойлер: у нас получилось. Поэтому если вы когда-либо сталкивались с вопросами «Как тестировать и предотвращать проблемы с SEO?» или «Как воскресить легаси сервис?», то эта статья для вас! Надеемся наш опыт вам поможет! А также будем рады, если в комментариях вы поделитесь своими мнениями и идеями. Давайте обсудим вместе!

Введение: что такое Pyro и почему мы его боялись

Pyro — это SEO-сервис, написанный на PHP 5, который хранит и отдаёт метатеги и другие SEO-данные для всего веба Иви. 

SEO в вебе — это не просто «технические настройки», а философия создания понятных, структурированных и ценных веб-ресурсов, которые: 

  • Люди могут легко читать и использовать

  • Поисковые системы могут правильно индексировать

  • Помогают владельцам получать стабильный трафик и клиентов

Наша задача: сделать сервис мультиязычным.

Проблема, с которой мы столкнулись: исторически специального параметра для языка не существовало. Все данные были на русском. Добавить поддержку языка, не сломав существующую сложную логику — вот главная задача. 

1. Знакомство с монстром: как работает Pyro

1.1. Размеры проблемы 

Pyro участвовал в формировании каждой страницы сайта. Если он падал или ошибался — мы теряли значительную часть поискового трафика на затронутых страницах. Для бизнеса это прямые потери. Для нас — жёсткий SLA.

Для понимания масштаба: одна страница → 5 запросов к Pyro. Всего роутов — 23, плюс параметризирующие GET-параметры. Ошибка в одном роуте — и трафик улетает. Вот как это выглядит на примере страницы /watch/...:

  • /group — общие метатеги для всех карточек контента (фильмы, сериалы)

  • /video — метатеги для конкретной карточки

  • /special_links, /menu — данные для шапки сайта

  • /meta — аналог robots.txt в head

Итого 5 запросов на одну страницу. А страниц — тысячи. Нагрузка на разработку и тестирование — колоссальная.

1.2. Логика работы сервиса в двух словах

Проблема: В текущей реализации нашего SEO-сервиса не была заложена поддержка нескольких языков. Данные хранятся в виде «ключ → значение», ключ строится из url а при запросе сервис собирает все ключи от общего к частному и склеивает ответы. Это порождает три классических эффекта:

  1. Протекание данных — общие ключи затирают частные

  2. Неявный приоритет — порядок обхода жёстко зашит в коде

  3. Трудность с новыми параметрами — они ломают всю логику мержа

Логика работы Pyro:

SEO-сервис работает по одной идее — «отдать хоть что-то и как можно больше». В нем на каждый GET-запрос выполняется рекурсивный обход базы данных. Ключ в базе данных собирается из url запрашиваемого ресурса. Например, если пользователь запросил url вида/menu?host=ivi.tv с параметром определенного хоста, то ключ в БД будет выглядеть как mask/menu/ivitv.

Пример ответа БД по ключу

Пример ответа БД по ключу - mask/menu:

{
    "110_text": "DEFAULT TEXT",
    "201_text": "DEFAULT TEXT",
    "111_linktitle": "ONLY IVI RU TEXT",
    "162_text": "DEFAULT TEXT",
    ...
}

Пример ответа БД по ключу - mask/menu/ivitv:

{
    "110_text": "DEFAULT TEXT",
    "201_text": "DEFAULT TEXT",
    "162_text": "ONLY IVI TV TEXT",
    ...
}

Ключ делится по слешам и ищется слева-направо. Например, в HTTP запросе к /menu/?host=ivi.tv, Pyro запросит ключ — mask/menu/ivitv, сделает два запроса в БД — по ключам mask/menu и mask/menu/ivitv, смержит ответы от БД и отдаст это в ответе.

{
    "110_text": "DEFAULT TEXT",
    "201_text": "DEFAULT TEXT",
    "111_linktitle": "ONLY IVI RU TEXT",
    "162_text": "ONLY IVI TV TEXT",
    ...
}

1.3. Как редакторские изменения SEO-данных попадают на прод

При необходимости внести какие-либо изменения в разметку страницы на сайте, редактор будет вносить ее непосредственно в файлы Pyro-updater.

Pyro-updater — это «админка» для работы с данными Pyro. Представляет из себя репозиторий в гите со специальным CI. Реплицирует структуру базы данных в файловом виде. Обновляет данные внутри Pyro («прожигает») посредством PUT-запросов из CI гита. Из yaml файла берется key — это url, по которому выполнится запрос и будет собираться ключ в БД, и value — это SEO-данные для конкретной сущности. Ниже представлен пример такого файла для главной страницы Иви:

Рис.1. Файл с SEO данными

Рис.1. Файл с SEO данными

Когда пользователь попадет на страницу, где менялась СЕО-разметка, сайт отправит GET-запрос на url указанный как ключ в файле Pyro-updater'а, и отдана информация обновленная из ранее описанного PUT-запроса. В целом, диаграмма работы Иви с сервисом выглядит следующим образом:

Рис.2. Silex-диаграмма работы сервиса со стороны редактора SEO-данных.

Рис.2. Silex-диаграмма работы сервиса со стороны редактора SEO-данных.

Рис.3. Silex-диаграмма работы сервиса со стороны пользователя Иви.

Рис.3. Silex-диаграмма работы сервиса со стороны пользователя Иви.

2. План спасения

Вместо одной большой задачи «Добавить мультиязычность в Pyro» мы создали эпик с чёткими шагами:

1. Исследование 
   - Разработчик: разобраться в логике работы, поднять локально
   - QA: провести аудит тестового покрытия, изучить баги за год, собрать список роутов Pyro по приоритету

2. Чистка данных 
   - Проверить на наличие неиспользуемых файлов

3. Автоматизация процессов
   - Подготовить скрипты для машинного перевода, поиска в базе файлов дубликатов и поиска отличий внутри языковых директорий

4. Добавление url-параметра языка в запросы сервиса — lang
   - Изменить логику формирования ключей

3. Разработка

3.1. Чистка данных: удаляем мусор

Репозиторий Pyro-updater (админка для SEO-данных) представлял из себя ~550 МБ чистого текста. Многие данные дублировались или не использовались. Существовали специальные параметры, дробящие пользователей на группы, например,authorized— деление на авторизованных и не авторизованных. 

Что вырезали:
- Файлы, содержащие в url устаревшие GET-параметры для авторизованных подписчиков и не подписчиков, что сильно увеличивало объем. 
- Устаревшие маски-плейсхолдеры для удалённых разделов сайта
- Дублирующиеся JSON-LD разметки — для этого был написан анализирующий скрипт, выдающий список файлов, где и что дублируется

Итог: минус ~150 МБ «мусора», упрощение структуры.

3.2. Добавление параметра языка lang: осторожно рекурсия

Главная особенность Pyro — рекурсивный обход + мерж данных. Если не осторожничать, русские данные «протекают» в другие языки через общие маски.

Наше ключевое решение: параметр lang не участвует в рекурсии.

Почему это было больно? Покажу на примере карточки контента.

Как работало раньше (без языков):

  • PUT в /group/{content}/ → {"title": "Общий заголовок"}

  • GET на /video/{id}/ → получает тот же заголовок (через мерж)

Как стало (с ?lang=uz):

  • PUT в /group/{content}/?lang=uz → ок

  • GET на /video/{id}/?lang=uz → пусто. Мержа для разных языков нет.

Последствия: 4 самых приоритетных роута Pyro начали отдавать заглушки вместо реальных данных. Редактор SEO был не в восторге — пришлось бы вручную заполнять теги для каждого контента.

Что дальше? Мы думали вернуть мерж обратно. Но поняли: переписывать логику мержа — значит рисковать всеми роутами. Этого мы себе позволить не могли, поэтому выбрали другой путь — разработали скрипты для массового перевода и контроля качества. 

3.3. Инструменты: скрипты для массового перевода и финального контроля качества

Для машинного перевода был использован уже знакомый команде сервис SmartCat. Мы написали скрипты для выполнения следующих задач:

  1. Подготовка данных для загрузки в SmartCat. Скрипт рекурсивно собирает большой объём глубоко вложенных файлов в единую структуру, делит их на мелкие чанки для загрузки в SmartCat (мы не можем загнать огромный объем данных за раз)

  2. Подготовка полученных от SmartCat переводов. Скрипт соединяет разделенные ранее блоки в единый файл и добавляет параметр языка переведенным данным

  3. Скрипт, сравниващий структуры данных в разных языковых директориях и копирующий недостающие файлы. Так мы решили проблему ручного ввода SEO-данных админом

  4. Проверка отсутствия кириллицы в финальных переводах для анализа корректности проделанной работы

4. Тестирование: восстанавливаем пирамиду для SEO-сервиса

Для начала поговорим о проверках, которые могут быть не очевидны для тех, кто раньше не работал с SEO.

4.1. Базовые проверки SEO для чайников

Кейс 1. Глазами бота

Зачем: если бот получит 500 или 404 — страница выпадет из индекса. Теряем трафик.

Как проверить:

  • Chrome DevTools → Network conditions → User agent → выбрать Googlebot Smartphone (или YandexBot)

  • Обновить страницу

На что смотреть:

  • Страница открылась

  • Метатеги на месте

  • Нет 500, нет 404

  • Нет редиректа (если хотите скрыть раздел — лучше 404)

Кейс 2. Сервис упал — что видит пользователь

Зачем: убедиться, что при падении Pyro страница не рассыпается.

Как проверить: заблокировать запрос к Pyro (сниффер), обновить страницу.

На что смотреть: страница открыта, метатеги заменены на статику (не пустые).

Кейс 3. Сервис упал — что видит бот

Зачем: бот должен получить 503 (временная проблема), а не 500. Это сохранит позиции в индексе.

Как проверить:

  • Заблокировать запрос к Pyro

  • Подставить User-Agent бота (Googlebot / YandexBot)

  • Обновить страницу

На что смотреть: страница отдаёт 503, а не 500

Кейс 4. Данные из Pyro дошли без искажений

Зачем: E2E-проверка, что фронтенд не перебил ответ сервиса.

Как проверить: для ускорения тестирования мы используем расширение SEO META in 1 CLICK, которое позволяет увидеть заполненные теги на странице. Также для проверки на мобильных устройствах мы используем собственное расширение, так как SEO META in 1 CLICK не позволяет проверить данные на телефоне.

На что смотреть: метатеги на странице = ответу Pyro.

Кейс 5. Проверка данных в JSON-LD 

Зачем: поисковые системы используют JSON-LD для понимания структуры страницы (фильм, сериал, персона, отзыв). Ошибки в разметке → неправильный сниппет в выдаче → падение кликов.

Как проверить:

  • Chrome DevTools → Elements → найти <script type="application/ld+json">

  • Или расширение SEO META in 1 CLICK (вкладка Structured Data)

  • Скопировать JSON и проверить валидатором (например, validator.schema.org)

На что смотреть:

  • Тип разметки соответствует содержимому страницы (MovieTVSeriesPerson и т.д.)

  • Обязательные поля (nameurlimage) не пустые

  • Нет синтаксических ошибок в JSON

Кейс 6. Наличие атрибута lang в HTML в соответствии с языком страницы

Зачем: поисковики учитывают lang при ранжировании для конкретного языка. Скринридеры используют его для выбора правильного произношения.

Как проверить:

  • Chrome DevTools → Elements → найти <html lang="..."> или <meta property="og:locale">

  • Либо вручную посмотреть исходный код страницы

На что смотреть:

  • Атрибут lang присутствует и соответствует языку страницы (ru, uz, en и т.д.)

  • Если страница мультиязычная — lang меняется при переключении языка

  • og:locale синхронизирован с lang

4.2. Подготовка к тестированию

В Иви подготовка к тестированию включает в себя множество этапов: аудит покрытия, изучение багов, изучение пользовательких сценариев, построение графиков по посещениям на страницах с сервисом, архитектура покрытия на основе полученных данных и т.д. Предлагаю бегло пройтись по этапам.

Аудит покрытия (шокирующие цифры) 

После небольшого экскурса в проверки SEO хотелось бы обсудить само тестовое покрытие, варианты его оптимизации. Понимаю, что перевернутой пирамидой тестирования никого не удивить, но хотелось бы этот пункт посвятить именно ей. У нас были написаны тест-кейсы на проверки для SEO, о которых я рассказала выше в пункте 4.1. Часть кейсов была покрыта автотестами, но достаточно ли нам этого? Как оказалось, нет, и вот почему:

  • Масштаб сервиса и важность передаваемой им информации (см.пункт 1.1) требуют покрыть все роуты хотя бы базовыми проверками. Например, проверка GET и PUT запросов, проверка мержа данных и т.д.

  • Как выяснилось долгое время в CI не запускались написанные ранее API и Unit-тесты, и это лишь часть проблемы

  • Уровень найденных авто, API и Unit тестов был совсем «базовый минимум». «Роскошного максимума» никто и не хотел, но важно было сократить количество ручных проверок:

    • Многие GET-параметры не были учтены, что явно добавляло ручных проверок с учетом параметризации

    • Мерж данных между разными роутами вообще не проверялся, хотя является одной из важнейших фич этого сервиса

    • Проверки рекурсивного обхода также не было

  • UI-тесты редко содержали в себе проверки полученных данных из Pyro. Часто падали, требуя ручной перепроверки

  • В ручных тест-кейсах проверялись лишь сценарии из пункта 4.1. То есть количество кейсов здесь не равно качественной проверке, т.к. напрямую сам сервис не проверялся

Состояние «до»:

  • Кейсы проходимые вручную: 57

  • UI-тесты: 93  

  • API-тесты: 70 

  • Unit-тесты: 131  

Главная проблема: Не соблюдается принцип пирамиды тестирования. Скорее у нас были хрень какая-то песочные часы. Много хрупких UI-тестов, мало стабильных API и Unit-тестов.

Построение графиков по посещениям страниц с сервисом

Для построения графиков использовали инструмент Grafana. Нашей целью было узнать:

  • На каких страницах больше всего пользователей

  • На какой платформе больше всего нагрузки

  • Как часто и какие боты к нам приходят 

После получения данных мы можем точно понять, какие роуты нуждаются в большем внимании при написании кейсов и каковы будут потери при пропуске каких-либо сценариев. В нашем случае самой важной оказалась карточка контента, а именно сериалы, на desktop версии сайта.

Архитектура тестового покрытия

Изучили баги, собрали сценарии от бизнеса. Тест-кейсы разделили на API (unit-тесты) и e2e (UI-автотесты). Примерная иерархия покрытия тест-кейсами:

  • Pyro

    • Общие проверки — проверки, подходящие любому роуту

      • GET-параметры — проверки не привязанные к роуту. Проверяются на списке роутов

      • Негативные проверки — общие негативные проверки, подходящие любому роуту

    • Роуты — кейсы учитывающие особенности одного отдельно взятого роута или его слияние данных с другим роутом

      • /route_1 

      • /route_2

Такая иерархия решает сразу несколько проблем в покрытии:

  1. Удобство прохождения ручных проверок и понятная параметризация в каждом сценарии. Теперь все кейсы, где неважен роут, а важен GET-параметр, вынесены в один блок.

  2. Только важные пользовательские сценарии внутри директорий по роутам. Что сокращает количество сценариев, а значит ускоряет регресс.

  3. Понятное распределение кейсов по уровням пирамиды и полноценное понимание уровня покрытия сервиса.

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

Автоматизация рутинных проверок

Автоматизация — одно из прекраснейших созданий разработки. Поэтому не пренебрегайте ею. Вместо ручной проверки метатегов расширением, мы написали скрипт, который:

  1. Ходит по списку важных страниц

  2. Запрашивает данные из Pyro для разных языков

  3. Сравнивает с эталонными шаблонами

  4. Формирует отчёт в CI

Итог всей проделанной работы:

  • Кейсы проходимые вручную: 37

  • UI‑тесты: 93 → 95 (удалили дублирующиеся, добавили важное)

  • API‑тесты: 70 → 164

  • Unit‑тесты: 131 → 315

Вывод: сместили фокус на низкие уровни, регресс ускорили в 3 раза. Также мы получили выхлоп в виде сокращения ЧЧ QA в 10 раз. Т.к. инженерам по обеспечению качества оставалось только проверить приоритетные роуты в Pyro.

5. Интересные нюансы разработки и тестирования для SEO

Особенность нашего приложения — BFF 

Фронтенд Иви имеет полностью самописный SSR с клиентскими переходами. Переходы на клиенте сделаны при помощи BFF, отдающим данные для запрашиваемой страницы. BFF запрос может не получить информацию о языке и отдать данные на русском. А это может подпортить пользовательский опыт из-за частичного перевода некоторых блоков на страницах.

Решение: Добавили язык в роут для всех внутренних запросов к BFF.

Проблемы с машинным переводом

Переводя большие объемы данных на новый язык мы столкнулись с проблемой, что SmartCat, умеющий работать с латиницей в обычных названиях и ссылках, не корректно считывал поле, содержащее список ссылок подряд. Он переводил его вместе с основным текстом, что приводило к ошибке 404 при переходах на клиенте. 

Решение: Проконсультировавшись с руководителем SEO было принято решение вырезать это поле во всех переводах. Дополнительно нам помог скрипт анализа кириллицы в финальных переводах из пункта 3.3. 

Пример покрытия для тестирование рекурсивного обхода и слияния данных

Рассмотрим на примере одного запроса: /group/category/{category}. Для начала пишем кейсы на основе данных, багов и пользовательских сценариев. Главные правила: Сначала постарайтесь ответить на вопрос "на какой уровень будет тест, если я его напишу?" и выбирайте уровень как можно ниже.

Рис.4. Кейс для итеграционного теста выше

Рис.4. Кейс для итеграционного теста выше

После всех мучений с кейсами создаётся задача на покрытие в коде, и если вы готовы писать тесты и умеете это делать, то не стесняясь приступайте! В нашем случае тесты написаны с использованием фреймворка phpunit.

Рис.5. Пример интеграционного теста

Рис.5. Пример интеграционного теста

5. Заключение

Количественные изменения:

  1. Тестовое покрытие — восстановили пирамиду тестирования (см. таблицу ниже)

  2. Мусор в Pyro-updater — больше нет дублирующих и неиспользуемых файлов. Вес снизился с ~550 до ~400 МБ

Качественные изменения:

  1. Pyro теперь мультиязычный — поддерживает ru, uz и готов к быстрому добавлению новых языков

  2. Понимание сервиса — у команды появилась документация и тестовое покрытие

  3. Стабильность — добавили в CI тесты, релизы перестали быть русской рулеткой благодаря тестовому покрытию

Наш главный неочевидный совет, который мы поняли к концу проекта:

Не пытайтесь понять весь легаси-сервис целиком. Вместо этого:

  1. Найдите одну самую важную страницу/ручку, которую сервис обслуживает (у нас — карточка контента)

  2. Напишите один сквозной тест на неё (запрос → ответ → отображение)

  3. Зафиксируйте результат как эталон

  4. Только потом расширяйтесь на другие роуты

Мы потратили 3 недели на полный разбор всех роутов и поняли, что 70% времени ушло на то, что почти не используется. Если бы начали с карточки контента, то сократили бы исследование в 2 раза.

Как итог: архив знаний по Pyro был воскрешён. Сервис заговорил на узбекском, а в перспективе — и на других языках. Теперь команда не боится его, готова к активному развитию и обновлению сервиса. Теперь у нас есть тесты, документация и CI. Сервис больше не чёрный ящик. Это история о том, что даже с самым забытым legacy-сервисом можно подружиться. Нужно только пройти через боль, гнев и тонны тестов. И помните: если у вас есть сервис, который работает, но его все боятся трогать, то велика вероятность, что однажды это придется сделать именно вам.

А теперь вопрос к вам: какой самый древний язык/фреймворк вы оживляли? Сколько лет было сервису? И главное — вы всё переписали или оставили как есть? Жду в комментариях ваши истории и стадии принятия. В общем, попрощаемся, как обычно: не стойте на месте, с удовольствием изучайте новое и улучшайте себя! До новых встреч на Хабре!