
Логирование действий пользователей — ключевой процесс для любого бизнеса, который важен как для оценки текущего состояния продукта, так и для статистики проводимых экспериментов и нововведений. Как результат, к точности сбора статистики пользовательских действий обычно предъявляются высокие требования, а для оперативного выявления любых проблем работа с логами обвешивается множеством проверок.
Меня зовут Елена Пранова. Я инженер по автоматизации тестирования в ОК. В этой статье я расскажу об автоматизации проверок логирования в ОК, особенностях работы с Kafka, написании тестов для проекта статистики и не только.
Вводное слово
Прежде чем перейти к подробностям хотелось бы немного сориентировать будущего читателя. Если вы уже знаете, что такое статистика, как она устроена и какие могут быть проблемы, но вы не знаете, как ее можно автоматизировать, то предлагаю сразу перейти к главе «Варианты внедрения автоматической проверки статистики», если уже анализировали возможности, то можно перейти к нашему решению «С каким „багажом“ мы начали реализацию». А если вы только начинаете знакомиться с этой темой, то предлагаю не пропускать пункты и начать с начала:)
Зачем нужна статистика в ОК
ОК — одна из самых популярных соцсетей рунета. Ежемесячная аудитория ОК достигает 36 млн пользователей из РФ.
Пользователям ОК доступен огромный набор действий и сценариев использования соцсети, который не ограничивается только общением. При этом мы непрерывно выпускаем новые функции и обновляем дизайн. При принятии решения ключевым фактором является реакция пользователей на нововведения или предложенные сценарии. Соответственно, чтобы иметь возможность объективно оценивать влияние любых нововведений, мы должны собирать данные о действиях пользователей для последующего анализа — например, чтобы понять:
пользуются ли люди функциями или нет;
что им интересно;
где самые высокие показатели, например, кликов на кнопку.
Упрощенно сам механизм сбора данных прост и понятен: пользователь кликает на кнопку «Искать на сайте», мы фиксируем эту информацию и передаем ее в определенный сервис, где с ней может ознакомиться аналитик.

Здесь важно понимать, что корректный сбор статистики по всем действиям в таком крупном и «живом» продукте, как ОК, имеет фундаментальное значение.
Для наглядности можно рассмотреть следующую ситуацию:
Мы в настройках переместили функцию «Черный список». После этого в техподдержку начали поступать массовые обращения с вопросами и просьбами вернуть всё на место. До ознакомления со статистикой складывалось ощущение, что перемещение функции привело к просадке метрик использования фичи. Но изучение данных показало, что подавляющее большинство людей без проблем продолжает пользоваться функцией и не испытывает трудностей с поиском «Черного списка». Фактически проблема была частной. Если бы не статистика, мы, скорее всего, неоправданно откатили бы обновление.
Помимо этого, статистика используется в ОК в ряде сценариев, в том числе для:
корректной работы внутренних сервисов — например, статистика нужна для правильного подбора рекомендаций (нет статистики — рекомендации не формируются);
проведения А/В-экспериментов с анализом метрик пользователей и влияния запусков на продукт и т.д.
Примечательно, что порядок сбора статистики у нас довольно типовой. На примере упомянутой кнопки он сводится к простым действиям:
менеджер заготавливает все имена;
разработчик добавляет в код заготовленные параметры для статистики;
тестировщик проверяет их корректное отображение.
Вместе с тем, здесь мы столкнулись с некоторыми барьерами.
QA много времени тратили на тестирование статистики. Сначала надо проверить фичу, потом статистику к ней. А если выявляются ошибки или фичу дорабатывают, цикл надо проходить повторно.
Легко ошибиться. Работа со множеством похожих параметров неизбежно связана с риском ошибок.
Много табличных данных. Все данные табличные, их очень много, а постоянно переключаться на них — то ещё приключение.
Рутина. Выполнение потока однотипных задач — максимальная рутина, от которой хочется отказаться.
Важность отсутствия багов. Есть ситуации, когда наличие минорных багов не критично, их можно отложить и пофиксить в следующих итерациях. Но при работе со статистикой нет минора — на всё высокий приоритет: либо вы будете видеть, что у вас с фичей, либо нет.
Оценив эти вызовы, мы поняли, что лучшим решением для данных задач будет автоматизация, которая снимет со специалистов лишнюю работу и снизит риск ошибок из-за человеческого фактора.
Как устроена статистика в ОК
После разбора проблематики перейдем к изучению того, как устроена статистика в ОК.
В работе со статистикой у нас задействованы:
сервисы и приложения, которые отправляют информацию о событии в статистические сервисы;
механизм, позволяющий нашим сервисам отправлять данные о событиях;
сервис-брокер сообщений Kafka, который используется как промежуточное хранилище данных при отправке событий;
сервисы, которые производят обработку сырых данных из Kafka и записывают их в HDFS;
сервис HDFS, в котором хранятся обработанные события из Kafka, разложенные по паркетам;
Zeppelin, позволяющий производить анализ данных, сохраненных в HDFS, а также, в случае тестовой среды, — проверять события, попадающие в Kafka.
Статистика, попадающая таким образом в хранилище, используется для разных задач. В том числе для подсчета A/Б-экспериментов.
Раньше у нас было полностью ручное тестирование без какой-либо автоматизации, а одним из ключевых артефактов тестирования для нас была именно статистика.
Проблемы и трудности
Проблемы со сбором статистики могут возникнуть на каждом этапе — от сбора данных до их отправления и обработки, потеря данных, ошибки, ограничения, блокировки и многое другое влияет на конечный результат. Рассмотрим ниже, что ещё может быть.
Время
Сервис статистики — отдельный сервис, поэтому агрегация продакшн статистики обычно выполняется в течение суток. То есть данные доступны не сразу.
Дублирование
Возможно дублирование статистики из-за некорректной отправки событий или других факторов. Это может влиять на точность и рациональность принимаемых бизнес-решений.
Дублирование сложно отлавливать в ручном режиме — фактически надо проверять каждый этап и компонент, отправляющий данные.
Неоднозначное поведение
Если на одном экране кнопка работает, отдает правильную статистику, то это не значит, что на другом экране такая же кнопка будет иметь такое же поведение. То есть надо тестировать всё и везде, проверять все сценарии, а это время и большая нагрузка на специалистов.
Возможные ошибки на бэке
Все этапы процесса разработки и внедрения новой фичи могут стать «бутылочным горлышком» или причиной проблемы. Не исключение и процессы бэкенда — например, разработчики могут неправильно собрать конфигурацию. В итоге тестировщик может делать всё правильно, но данные будут приходить ошибочные. Причем в ручном режиме тестировщик зачастую не сможет самостоятельно определить первоисточник проблем.
Высокое влияние на продуктовые эксперименты
От точности получения данных статистики зависит корректность оценки влияния новых фич или обновлений на продакшене и продовые метрики. Поэтому, если статистика пишется неправильно, мы не можем считать эксперимент успешным и вынуждены его запускать повторно. При этом нередко у нас продуктовые эксперименты идут длительное время. То есть любой повторный запуск неизбежно сказывается на time-to-market и объеме затраченных ресурсов, а в условиях жестких дедлайнов может быть вообще недопустим.
В какой-то момент влияние этих рисков стало для нас критическим. И мы приняли решение автоматизировать проверку статистики, чтобы нивелировать любые потенциальные проблемы.
Варианты внедрения автоматической проверки статистики
Есть несколько вариантов автоматизации проверки тестов статистики. Поговорим немного о каждом.
Код разработчика
Для получения данных для статистики можно анализировать код разработчика — открывать его и сверять каждое фактическое значение с ожидаемым.
Подход имеет право на жизнь, но он довольно специфический:
процесс трудоемкий;
легко ошибиться и не всегда понятно, как работает код;
нет гарантий, что всё будет работать стабильно и предсказуемо;
не подходит для автоматизации.
Графики
Следующий вариант — графики. Например, у нас есть самописный сервис, где по графикам мы можем отследить часть информации и метрик. Но он больше ориентирован на просмотр общей картины, а не проверки атомарных действий.

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

И здесь же практически на лету можно сделать нужную автоматизацию.
Например, с Java и Selenium это можно реализовать набором простых команд:
// Получаем DevTools
DevTools devTools = driver.getDevTools();
devTools.createSession();
// Включаем перехват сетевых запросов
devTools.send(Network.enable());
// Добавляем слушатель на события ответа
devTools.addListener(Network.responseReceived(), response -> {
res = response.getResponse();
url = res.getUrl();
status = res.getStatus();
});
driver.get("https://example.com");
// Совершаем действия
// Проверяем
В целом, подход выглядит, как готовый рабочий вариант — он позволяет быстро просматривать события и даже подходит для автоматизации. Более того, у него есть несколько важных преимуществ.
Автоматизация позволяет быстро и точно собирать данные о сетевых запросах.
Снижается вероятность человеческих ошибок при извлечении данных.
Возможность автоматизированного анализа и обработки больших объемов сетевых данных помогает выявлять узкие места и оптимизировать производительность.
Можно интегрировать тестирование сетевых запросов в процессы CI/CD.
Есть и несколько недостатков.
Настройка автоматизации может потребовать значительных усилий и знаний, особенно для сложных сценариев.
Зависимость от сторонних библиотек и инструментов может привести к проблемам с безопасностью, если они не будут правильно настроены.
Автоматизированный сбор данных может привести к получению большого объема информации, что может затруднить анализ и извлечение полезной информации.
То есть вариант с консолью подходит, но работа с ним не лишена нюансов. Поэтому стоит рассмотреть и другие варианты.
Прокси
Данные также могут приходить через прокси. И даже здесь с ними можно работать. Для этих задач можно использовать, например, библиотеку BrowserMob Proxy. Решение хорошо работает как автономный прокси-сервер, но инструмент особенно полезен при встраивании в тесты Selenium.
В целом, это тоже рабочий вариант. Мы можем спокойно запустить прокси, добавить необходимые фильтры, посмотреть, как всё пришло, обработать данные и получить результат.
@Test
public void runBrowserWithProxy() throws MalformedURLException {
ChromeOptions options = new ChromeOptions();
options.addArguments("--ignore-certificate-errors");
DesiredCapabilities capabilities = new DesiredCapabilities("chrome",
"128.0.6613.137", Platform.ANY);
capabilities.setCapability(ChromeOptions.CAPABILITY, options);
capabilities.setCapability(CapabilityType.PROXY,
ClientUtil.createSeleniumProxy(proxy));
driver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"),
capabilities);
driver.get("https://ok.ru/");
}
Ключевое преимущество метода в том, что для его реализации есть готовое решение. Но это же и является недостатком — вместе с готовой библиотекой мы получаем жесткую зависимость от нее, чего хотелось бы избежать.
Сервис-брокер
Следующий этап движения данных — сервис-брокер.
Здесь будет максимально честная проверка, поскольку именно в брокере накапливается вся нужная информация о событиях. Причем сам брокер может быть как самописный, так и общедоступный. Например, самыми популярными брокерами являются RabbitMQ и Apache Kafka.
Реализовать автоматизацию на этом этапе также возможно.
Но крайне важно обеспечить достоверность данных и быть готовыми к тому, что получить доступ к брокеру не всегда возможно, да и время ожидания событий иногда может быть долгим.
База данных
Далее данные проходят через какой-либо статический обработчик и попадают в нашу базу данных. Фактически это лучший этап для автоматизации любых проверок, поскольку все данные агрегированы в одной системе.
Здесь для автоматизации лучше иметь секретный API для безопасного и комфортного использования, но зато можно легко выстроить автоматизацию и получить максимально корректную проверку.
Единственный барьер в этом случае — зависимость от разработчиков, у которых может не оказаться времени и ресурсов на создание Secret API.
Статистика UI
Ну и самое последнее, где можно перехватить и увидеть данные — статистика UI в браузере.

Поскольку здесь агрегируются все данные и даже выводятся результаты, проверка на этом этапе будет самой честной. Но, вместе с тем, выстраивать автоматизацию здесь сложно, долго и дорого, поэтому совершенно нецелесообразно.
Что в итоге
Таким образом, если проанализировать все этапы, можно сделать несколько выводов:
Автоматизация возможна и оправдана во всех случаях кроме UI.
Быстрое решение можно реализовать в консоли браузера и прокси.
Честная проверка будет в сервис-брокере, Secret API и UI.
Для реализации в сервис-брокере, Secret API и статистике UI нужны дополнительные ресурсы.
Таким образом, можно получить либо быструю, но нечестную проверку, либо честную, но медленную. Что здесь выбирать — решайте сами. Это зависит от того, чем вы готовы пожертвовать точностью или скоростью.
С каким «багажом» мы начали реализацию
Внедрение автоматизации в тестирование статистики с нуля — очевидно сложный и трудозатратный процесс. Поэтому наиболее рациональный подход — применить те сценарии и решения, которые уже используются в проекте. Это наиболее оптимальный и рентабельный сценарий как с точки зрения подготовки таких автотестов, так и с точки зрения затрачиваемых ресурсов.
У нас в ОК довольно много автотестов под разные платформы и задачи. Соответственно, есть принятый стек и выработанные паттерны работы с ними.
Поэтому найти отправную точку для автоматизации нам было проще — за основу мы взяли наработанные автотесты, а для автоматизации выбрали Selenide, который способен имитировать действия пользователей и быстро проверять нужные сценарии.
К моменту внедрения проверок логирования пользовательских действий мы подходили достаточно подготовленными. Так, у нас были:
проработанная архитектура тестов;
готовая инфраструктура под автотесты;
большой набор тестов, разделенных между командами;
выстроенная система удаленных параллельных запусков;
собственный сервис регистрации ботов и их последующего освобождения;
CI/CD;
внутренняя система отчётности.
У нас:
более 3700 API-автотестов, которые проходят за 5 минут;
свыше 3500 WEB-автотестов, на прогон которых надо примерно 10 минут;
больше 1400 MOB/WEB-автотестов, которые проходят за 7-8 минут.
При этом наш стек для работы с автотестами включает:
Java;
Selenide;
JUnit5;
систему регистрации ботов;
самописный раннер, который гоняет тесты параллельно;
самописный сервис отчётности;
TeamCity;
PageObject и другие паттерны.
Выглядит гораздо проще, чем все делать с нуля.
Но остается открытым вопрос, куда именно встроить проверку тестов статистики и какой вариант автоматизации тестов статистики выбрать.
Что сделали в ОК
Мы остановились на промежуточном варианте — на сервисе-брокере. Для нас это Kafka, которая позволяет отправлять, хранить и обрабатывать большие объемы данных в реальном времени.
У Kafka есть несколько основных компонентов.
Producer — приложение или сервис, который отправляет (публикует) данные в Kafka.
Consumer — приложение или сервис, который получает (подписывается на) данные из Kafka.
Broker — сервер, который хранит данные и обрабатывает запросы от producers и consumers.
Topic — логическая категория, в которую записываются сообщения. Producers отправляют данные в topic, а consumers читают из него.
Consumer в Kafka — это клиентское приложение, которое подписывается на один или несколько topics и получает сообщения из них.
Consumers могут подписываться на один или несколько topics, чтобы получать сообщения из них.
Consumers могут объединяться в группы, что позволяет распределять нагрузку.
Consumers могут обрабатывать данные в реальном времени, что делает Kafka подходящим для сценариев, требующих быстрой реакции на события.
Мы выбрали открытый org.apache.kafka:kafka-clients:2.3.0.
Нюансы реализации
Kafka находится на продакшен сервисе. Получить доступ к нему непросто, поэтому на получение данных может уходить много времени. Чтобы исключить это, мы использовали тестовый стенд ОК, а также запросили и получили от разработчиков отдельные тестовые кластеры Kafka.
Из-за более низкой нагрузки (в сравнении с продакшен кластерами), на них время ожидания данных составляет примерно минуту. Что также важно для нас — у нас есть возможность получить отдельные доступы для проекта.
Как устроено
Теперь подробнее остановимся на том, как именно у нас всё устроено.
Для каждого теста в Before своя логика. Алгоритм примерно следующий:
Создаем
getKafkaConsumer()
с определенными пропертями в отдельном потоке.Далее создаем топики
getKafkaTopics()
. Подписываемся на них.Далее идет цикл ожидания событий, который прерывается после завершения теста.
Помимо этого, у нас также может быть встроенное ожидание.
Всё это крутится в определённом цикле и собирается.
@BeforeEach
void before() {
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<EventsAssert> future = executor.submit(
KafkaConsumer<byte[], byte[]> consumer = getKafkaConsumer();
final Collection<String> topics = getKafkaTopics();
consumer.subscribe(topics);
while (running.get()) {
ConsumerRecords<byte[], byte[]> poll =
consumer.poll(Duration.ofSeconds(0));
for (ConsumerRecord<byte[], byte[]> record : poll) {
events.add(record.value());
}
} ); //...
executor.shutdown();}
Мы используем Group ID в свойствах, задавая имя и класс, чтобы каждый тестовый класс, который что-либо совершает, мог получить нужные ему данные.
Properties conf = new Properties();
String servers = getServers();
conf.put("bootstrap.servers", servers);
conf.put("metadata.broker.list", servers);
conf.put("group.id", this.getClass().getName() + RandomStringUtils. (3))
Данные топика
Поговорим немного о данных. Для удобства и наглядности я свела все данные топика в таблицу.

Важный момент, что топики в Kafka не создаются автоматически, поэтому для создания топика нужно вручную указать:
название топика;
объекты сообщения, которые будут отправлять в этот топик;
объемы данных, которые будут поступать в топик;
данные о том, необходима ли паркетизация для топика, а также ее параметры.
С механикой подписки мы определились.
Теперь важно определить, как тест будет понимать, какие именно данные ему нужны — чтобы он не забирал лишние данные и не пропускал нужные.
Это решается с помощью персонального идентификатора, в качестве которого мы применяем простые уникальные куки для каждого теста. С их помощью мы можем не только «показать» тесту, какие данные ему нужны, но и в дальнейшем фильтровать их по тесту и идентификатору. Кроме того, наличие идентификатора помогает сразу отлавливать дублированные данные.
После определения нужных данных, тест проверяет:
location
;target
;context
;type
.
Также в конце теста сравниваем ожидаемое с фактическим.
Таким образом, по мере прохождения теста он «обвешивается» различными проверками.

Дополнительные нюансы
Помимо упомянутого, особенностью нашей реализации является переиспользование автотестов.
Так, мы можем задействовать тесты, которые у нас уже были под разные платформы, и просто подключаем их к себе в проект с автотестами как зависимость.
При этом в случае обновлений и изменений мы собираем новую версию и автоматически подтягиваем ее в проект с тестами статистики.
Теперь об отчётности.
Мы создаем отдельный отчёт под каждый тест (один тест — один отчёт). Это наиболее удобный и информативный вариант. Сам формат отчёта может быть разным. Это связано с тем, что отчёты могут смотреть разные пользователи с разными запросами. Например, тестировщикам удобнее смотреть отчёты со скринами, шагами и всеми подробностями теста, а аналитикам и менеджерам комфортнее работать с отчётом, в котором входные данные упрощены, а результаты теста отображаются флажками, которые указывают на успешное прохождение или фейл.
Полученные результаты и выводы
В структуре, которая реализована у нас, проверка статистики проходит через все этапы и компоненты — от автотестов до отчётности. В результате на каждое изменение, на каждый коммит разработчика мы можем отслеживать корректность ведения статистики. Таким образом мы обеспечиваем полную прозрачность всех отслеживаемых действий и гарантируем достоверность данных.
В дальнейшем подобные проверки можно встроить в обновление — этим пользуются и тестировщики, и разработчики, так как можно даже без просмотра фичи тестировщика запустить тесты, и убедиться, что в статистике ничего не сломалось и всё проходит корректно.
Из своего опыта мы сделали несколько выводов:
Автоматизировать статистику можно и нужно — это важно как бизнесу, так и разработчикам для выявления проблем и их устранениях.
Проверки логирования действий пользователей можно строить на базе уже имеющихся тестов, поэтому порог входа здесь ниже, чем кажется.
Если автоматизации ещё нет — стоит заняться ее развитием. Сначала вы работаете на автоматизацию, а потом автоматизация работает на вас.