инженерная культура

Тестирование CLI-приложений без костылей: единый фреймворк вместо десятка утилит

Изображение создано
с помощью нейросети
инженерная культура
97
0
11 сентября 2025
Тестирование CLI-приложений без костылей: единый фреймворк вместо десятка утилит
Изображение создано с помощью нейросети
97
0
11 сентября 2025

Готовые утилиты в области систем хранения данных зачастую не обеспечивают полного покрытия тестовых сценариев или ориентированы только на специфические задачи. Проверить массив из десятков или сотен дисков, учесть разные конфигурации железа и операционных систем, автоматизировать все до одного клика — такие задачи стандартные инструменты просто не решают. Перед инженерами встает выбор: продолжать вручную собирать «конструкторы» из разрозненных утилит или создать собственный инструмент. Так у нас появился кастомный фреймворк для тестирования CLI-приложений, который позволяет проводить различные виды тестирования, обеспечивает работу с оборудованием и предоставляет понятную отчетность в одном решении.

Артём Хюппенен, инженер по тестированию в YADRO, поделился техническими деталями фреймворка и примерами из практики: как выбирали технологии, что оказалось удачным и как теперь любой член команды может быстро автоматизировать тесты для сложных CLI-приложений. В конце статьи — ссылка на репозиторий, чтобы посмотреть архитектуру на практике.

Повторим основы

С системами хранения данных мы сталкиваемся почти каждый день, даже не задумываясь об этом. Фотографии в облаке, сообщения в мессенджерах, огромные базы данных соцсетей, потоковые сервисы, интернет-провайдеры, которые где-то должны хранить весь трафик, — все это работает благодаря системам хранения.

В основе любой СХД лежат накопители: жесткие диски, CD или SSD. Для того чтобы обеспечить скорость и надежность, их объединяют в RAID-массивы — логические устройства, построенные на основе технологии RAID. Если коротко, RAID — это технология, позволяющая объединить физические диски в одно логическое устройство и организовать хранение данных на нем так, чтобы достичь определенного баланса между скоростью, надежностью и эффективностью использования места. Что такое T-RAID и как он устроен в линейке СХД TATLIN мы уже писали.

Существует множество уровней RAID, но для примера мы возьмем такие:

  • — RAID 0 — объединяет диски в один большой. Это дает высокую скорость, потому что данные пишутся и читаются параллельно, но надежности тут нет совсем: сломается один диск — потеряются все данные.
  • — RAID 1 — дублирует данные на два диска. Если один выйдет из строя, второй продолжит работу, но при этом половина дискового пространства уходит под копию.
  • — RAID 5 и выше — более сложные схемы, где диски обмениваются информацией о соседях. Если один диск ломается, данные можно восстановить. Скорость при этом остается высокой, потому что запись идет на несколько накопителей одновременно.

RAID — основа нашей работы. Тестируя СХД, мы проверяем не только сохранность данных, но и реакцию системы на сбои: если один диск выходит из строя — данные остаются целыми и доступны для чтения. Если падает производительность — нужно искать причину.

Главная проблема: большинство доступных инструментов для тестирования ориентированы на узкие задачи и не подходят для проведения комплексных нагрузочных сценариев — с моделированием отказов, нестандартных ситуаций и других стресс-кейсов. Именно это стало отправной точкой для идеи создать собственный фреймворк.

18 сентября в Санкт-Петербурге Артём будет выступать на QA-митапе YADRO. В программе целостность данных и реальные кейсы создания интеграционных тестов для сложных распределенных систем. Можно присоединиться как офлайн, так и онлайн, но нужно зарегистрироваться.

Конструктор из утилит: рабочий, но неудобный путь

Когда мы только начали думать о том, как тестировать наши СХД, первое, что пришло в голову, — взять готовые утилиты с рынка. Их немало, и многие из них знакомы даже людям, которые далеки от тестирования.

CrystalDiskMark — простая утилита с понятным интерфейсом: запустил, нажал кнопку и сразу видишь красивые цифры скорости чтения и записи. Удобно и наглядно, но хорошо работает для одного-двух дисков на домашнем ПК. Когда же речь идет о массиве из десятков накопителей и требуется автоматизация тестов, возможностей CrystalDiskMark конечно же не хватает.

Когда речь идет о большой системе с десятками накопителей, где нужно измерить производительность целиком, возможностей CrystalDiskMark уже недостаточно.

Результаты теста CrystalDiskMark для твердотельного накопителя Samsung
Результаты теста CrystalDiskMark для твердотельного накопителя Samsung

Есть и более «серьезные» варианты — CLI-утилиты вроде FIO или VDBench. Они работают в терминале, поддерживают множество параметров и позволяют тонко настраивать нагрузочные тесты.

Показатели производительности по тестам FIO
Статистика axboe/fio

Я уверен, что многие инженеры, которые хоть раз тестировали диски в Linux, с FIO сталкивались. Мы его тоже используем, и он прекрасно подходит для генерации нагрузки.

Еще один важный инструмент в нашем стеке — pgbench. Эта утилита создает нагрузку на PostgreSQL-базы данных, развернутые на тестируемых СХД, что позволяет оценить влияние дисковой подсистемы на работу реальных приложений. Ее сильные стороны — простота моделирования типичных сценариев — например, банковских транзакций и гибкость в настройке интенсивности запросов. Благодаря этому pgbench помогает выявить «узкие места» при интеграции СХД с enterprise-приложениями.

PostgreSQL benchmark с использованием pgbench

Однако у метода есть ограничение: тестирование ведется на уровне СУБД, а не на уровне блоков, поэтому результаты зависят не только от скорости дисков, но и от конфигурации самой базы данных. Для анализа «голой» производительности накопителей без накладных расходов СУБД этот инструмент не подходит.

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

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

Решение создать свой инструмент возникло из потребности. У нас специфические сценарии тестирования, которые не покрывал ни один готовый инструмент. Например, у разных заказчиков — разные конфигурации СХД: разное «железо», разные Linux дистрибутивы и версии ядер. Иногда система даже ведет себя по-разному в зависимости от типа дисков или конкретных драйверов. В таких условиях нужно, чтобы инструмент тестирования мог быстро подстраиваться под любую среду.

Вторая проблема — интеграция с оборудованием. CLI-утилиты вроде FIO и VDBench генерируют нагрузку на уровне блочных операций, но не предоставляют встроенных средств для мониторинга или управления СХД — например, проверки состояния дисков. Это вынуждает вручную «склеивать» тестовую нагрузку с внешними утилитами — запускать параллельные процессы, синхронизировать логи и интерпретировать данные из разных источников. Это не только неудобно, но и увеличивает риск ошибок: легко запутаться, где закончился один инструмент и начался другой.

Третья задача — отчетность. Для инженеров лог с кучей цифр — это нормально. Но для менеджера или заказчика нужен отчет с графиками, понятными выводами и итоговыми оценками. Готовые утилиты этим не занимались: максимум, что они делают, — выводят результаты в консоль или сохраняют их в файл.

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

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

В итоге мы пришли к выводу: нам нужен единый инструмент, который:

  • — Умеет взаимодействовать с CLI-приложениями и самим оборудованием.
  • — Может управлять нагрузочными тестами и проверками целостности данных.
  • — Поддерживает гибкую настройку под разные конфигурации.
  • — Генерирует понятные и детализированные отчеты.
  • — Легко интегрируется в наши процессы автоматизации.

Так и родилась идея кастомного фреймворка, который объединит все это и станет универсальной платформой для тестирования наших систем хранения данных.

3 PM: Python, Paramiko, PyTest и Mapper

Осознав, что готового решения нет, мы сосредоточились на выборе инструментов, которые подходят команде и позволяют быстро развивать проект. Мы выбрали Python и вот почему:

  • — Низкий порог входа — даже новичку проще освоить базовые конструкции Python.
  • — Популярность среди тестировщиков — язык уже стал «дефолтным» для автоматизации тестов, вокруг него большое сообщество, множество библиотек и плагинов.

Для удаленного управления тестируемыми системами мы использовали Paramiko — проверенную библиотеку для работы с SSH в Python. Поскольку большинство СХД управляются через терминал, этот инструмент оказался идеальным: он позволил реализовать удаленное выполнение команд и получать результаты прямо в рамках фреймворка.

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

В основе фреймворка — mapper, абстракция над CLI-командами, которая предоставляет естественный Python-интерфейс для работы с CLI операциями СХД. Вместо ручного конструирования терминальных команд мы оперируем методами, структурно повторяющими синтаксис CLI, но с преимуществами языка программирования: типизацией, валидацией параметров и интеграцией в кодовую базу.

Например, вместо вызова в терминале:

С помощью mapper эта же операция в автотестах будет выглядеть так:

Ключевая идея: mapper не «упрощает» CLI-команды, а зеркалит их логику в виде читаемого и поддерживаемого кода. Это позволяет инженерам писать тесты на Python, сохраняя эмулирование привычного «терминального ввода команд».

Что еще должно быть во фреймворке

Есть еще несколько моментов, которые мы предусмотрели в фреймворке.

Система логирования с понятными и структурированными записями

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

  • — Понятность и структурированность. Логи должны быть написаны простым, читаемым языком. Важно, чтобы в них можно было быстро разобраться, а не сталкиваться с «полотном текста», как в Java-трейсах.
  • — Достаточность без избыточности. В логах должна оставаться только полезная информация. Лишний «мусор» лучше фильтровать, чтобы не перегружать отчет и ускорить диагностику проблем.

Интерфейс, отвечающий за удаленное подключение

Мы работаем не с локальными машинами, а с полноценными системами хранения данных, которые размещены где-то в серверных. Чтобы взаимодействовать с ними, нужен инструмент, позволяющий подключаться к удаленной машине по ее IP-адресу. И с этой задачей помогает библиотека Paramiko, в которой уже реализованы все необходимые нам методы. Нам только нужно на ее основе реализовать свой класс-интерфейс для взаимодействия с удаленными нодами.

Основная задача интерфейса — уметь подключаться к удаленной машине по SSH и выполнять команды. Для этого достаточно задать базовые параметры: IP-адрес, логин, пароль и, при необходимости, порт. Главным элементом здесь является функция exec (сокращение от execute). Она подключается к удаленному серверу, выполняет переданную команду и возвращает результат: успешное выполнение, ошибки, а также дополнительную информацию, с которой можно работать дальше. Как выглядит реализация:

Фактически Paramiko уже реализует метод exec_command, поэтому писать свою реализацию с нуля не нужно. Все, что требуется, — передать команду в метод: он отправит ее на сервер, выполнит, вернет вывод и код завершения. В Linux код 0 означает успешное выполнение, 1 — ошибку (аналогично тому, как в API принято 200 и 500). Для целей логирования это особенно важно: можно зафиксировать саму команду, ее вывод, время выполнения и результат. Например:

Всего несколько строк кода позволяют покрыть до 80% задач логирования и взаимодействия с системой:

Посмотрим на результаты. В первой строке лога фиксируется IP-адрес машины и сама выполненная команда.

Во второй — результат ее выполнения и время работы. Далее выводится полный результат выполнения (output), аналогичный тому, что мы получили бы, запустив команду вручную в терминале. Например, при вводе ls -l в каталоге root мы увидим список файлов, тот же самый список появится и в логах.

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

Mapper для работы с командами Linux терминала

Он позволяет выполнять самые разные действия: создать каталог, перезагрузить систему, вывести содержимое логов или, например, использовать стандартные команды вроде cat или ls:

В Linux-mapper можно собрать любой набор команд, которые применяются в терминале при тестировании приложения или при работе со сторонними утилитами.

Mapper для работы с разрабатываемым приложением

Кроме работы с терминальными командами, во фреймворке нужен интерфейс для взаимодействия с самим приложением, которое разрабатывают наши инженеры и которое мы тестируем. CLI-интерфейс для управления СХД, реализован в виде утилиты eracli. Он позволяет инженерам управлять RAID-массивами, системными настройками и т. д. При помощи mapper мы зеркалируем этот интерфейс в наш фреймворк:


В коде у метода есть подметоды — create, destroy, show и другие:

Благодаря такому подходу формируется удобный и понятный интерфейс: есть родительский класс с методом raid, а у него — набор подметодов для управления рейдами:

На этом возможности фреймворка не ограничиваются. В него можно добавить любые дополнительные утилиты, которые необходимы для тестирования. Это могут быть как сторонние библиотеки, так и отдельные приложения для нагрузочного тестирования — например, FIO или VDBench, которые позволяют создавать нагрузку на систему и проверять целостность данных.

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

Пишем тесты

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

Простой тест-кейс

Для начала набросаем тест-кейс «на бумаге»:

  1. Создать рейд массив.
  2. Проверить, что массив создался и отображается в системе.
  3. Выполнить штатную перезагрузку сервера.
  4. После перезагрузки проверить, проверить что массив не удалился и отображается в системе.

Во фреймворке это превращается в довольно понятный любому тестировщику код:

Для тестировщика это читается почти как обычная инструкция: «создай RAID и проверь, что он есть».

Идем дальше: действия с системой

Теперь представим, что мы хотим проверить, что RAID остается в рабочем состоянии после перезагрузки системы. В CLI это целая цепочка команд. В нашем коде:

Метод node.reboot () под капотом подключается к нужной машине по SSH, отправляет команду на перезагрузку и ждет, пока система снова будет доступна. Это и есть mapper для работы с командами Linux-терминала — фактически интерфейс нашей машины:

Например, мы можем удаленно вызвать у нее метод reboot, дождаться полной загрузки системы и затем убедиться, что все прошло успешно. В частности, проверяем, что после перезагрузки RAID-массив сохранился и не разрушился.

Автоматизация тестов

Автоматизацию тестов можно значительно упростить и сделать элегантнее с помощью Pytest. Рассмотрим один пример. Допустим, мы хотим проверить создание RAID-массивов разных уровней — не только нулевого или пятого, но и других. Если писать такие тесты без особенностей Pytest, получится много однотипного кода, что противоречит хорошему стилю программирования. Нам нужно избежать дублирования, и здесь помогает параметризация.

Для этого используется классический декоратор @pytest.mark.parametrize. В нем мы передаем список параметров, которые будут подставляться в тест. В нашем случае это уровни RAID: 0, 1, 5, 50, N+M. Каждый запуск теста берет очередное значение из списка и выполняет проверку:

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

Вместо заключения

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

Мы обеспечили поддержку многих популярных Linux-дистрибутивов и основных версий ядер — 4, 5 и 6 серии. Это делает фреймворк гибким и применимым в разных окружениях.

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

У такого собственного инструмента, конечно, есть и плюсы, и минусы.

Таблица плюсов и минусов

Код фреймворка доступен на GitHub. Любой желающий может посмотреть, как он устроен, и при необходимости адаптировать под свои задачи. Изначально проект появился как решение конкретной проблемы, но сам подход подойдет каждому, кто сталкивается с тестированием сложных систем через CLI.

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

Наверх
Будь первым, кто оставит комментарий