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

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

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

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

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

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

Существует множество уровней RAID, но для примера мы возьмем такие:
  • RAID 0 — объединяет диски в один большой. Это дает высокую скорость, потому что данные пишутся и читаются параллельно, но надежности тут нет совсем: сломается один диск — потеряются все данные.

  • RAID 1 — дублирует данные на два диска. Если один выйдет из строя, второй продолжит работу, но при этом половина дискового пространства уходит под копию.

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

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

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

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

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

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

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

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

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

Статистика axboe/fio
Статистика axboe/fio

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

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

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

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

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

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

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

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

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

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

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

  • Умеет взаимодействовать с CLI-приложениями и самим оборудованием.

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

  • Поддерживает гибкую настройку под разные конфигурации.

  • Генерирует понятные и детализированные отчеты.

  • Легко интегрируется в наши процессы автоматизации.

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

3 PM: Python, Paramiko, PyTest и Mapper

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

  • Низкий порог входа — даже новичку проще освоить базовые конструкции Python.

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

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

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

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

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

eracli raid create --name my_new_raid --level 0 --drives /dev/sda /dev/sdb

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

eracli.raid.create(name='my_new_raid', level=0, drives=2)

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

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

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

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

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

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

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

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

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

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

class SshConnect:
    def __init__(self, ip_addr: str, user: str, password: str, port: int = 22):
        self.ip_addr = ip_addr
        self.user = user
        self.password = password
        self.port = port
        self.__client = paramiko.SSHClient()
        self.__setup_client()

    def __setup_client(self): …
    def exec(self, cmd: str) -> BashResult: …

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

def exec(self, cmd: str) -> BashResult:
        stdin, stdout, stderr = self.__client.exec_command(cmd)
        cmd_output = stdout.read().decode('utf-8')
        exit_status = stdout.channel.recv_exit_status()
        return BashResult(cmd, cmd_output, exit_status, self.ip_addr)

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

def exec(self, cmd: str) -> BashResult:
        logger.info("[%s] cmd: [%s]", self.ip_addr, cmd)
        start_time = time.time()

        stdin, stdout, stderr = self.__client.exec_command(cmd)
        cmd_output = stdout.read().decode('utf-8')
        exit_status = stdout.channel.recv_exit_status()

        logger.info(f'cmd exit code: {exit_status}. Run time: '
                    f'{time.time() - start_time:.2f} s \n\n'
                    f'{cmd_output.decode("utf-8")}')

        return BashResult(cmd, cmd_output, exit_status, self.ip_addr)

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

17:53:35 [INFO] [172.16.23.122] cmd: [ls –l /root/]
17:53:36 [INFO] cmd exit code: 0. Run time: 0.12 s 

total 16
-rw-r--r-- 1 root root  395 Nov 16  2022 lic.txt
drwxr-xr-x 2 root root 4096 Mar 18 15:02 mounted
drwx------ 3 root root 4096 Nov 10  2022 snap

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

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

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

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

class AbstractLinuxMapper:
    …
    def exec(self, *args) -> BashResult: …
    def mkdir(self, *args)  -> BashResult: …
    def reboot(self) -> None: …
    …
    def your_func(self, *args): …

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

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

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

class EracliMapper:
    @property
    def raid(self) -> EracliRaid:
        …
        return EracliRaid(*args)

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

class EracliRaid():
    def create(*args): …
    def destroy(*args): …
    def show(*args): …

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

eracli.raid.create(name='my_new_raid’, level=0, drives=2)

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

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

Пишем тесты

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

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

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

  1. Создать рейд массив.

  2. Проверить, что массив создался и отображается в системе.

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

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

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

def test_create_raid_():
    eracli.raid.create(name=‘test_raid’, level=5, drives=4)
    assert eracli.raid.show()

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

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

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

def test_create_raid_():
    eracli.raid.create(name=‘test_raid’, level=5, drives=4)
    assert eracli.raid.show()

    node.reboot()

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

class AbstractLinuxMapper:
    …
    def reboot(self) -> None: …
    …

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

def test_create_delete_raid_():
    eracli.raid.create(name=‘test_raid’, level=5, drives=4)
    assert eracli.raid.show()

    node.reboot()

    assert eracli.raid.show()

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

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

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

@pytest.mark.parametrize('raid_level’, [0, 1, 5, 50, ‘nm’])
def test_create_all_raid_types_(raid_level: str | int):
    eracli.raid.create(name=‘test_raid’, level=raid_level, drives=8)
    …

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

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

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

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

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

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

Плюсы

Минусы

Возможность тестировать любые терминальные приложения с CLI-интерфейсом

Нужно уметь писать код: команда должна обладать не только навыками тестирования, но и навыками разработки

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

Высокая сложность backend-фреймворка: чтобы тесты были простыми, нужно продумать и реализовать сложную основу

Возможность быстро создавать большое количество тестов, повышая надежность системы

Риск ошибок в самом фреймворке: автоматизаторы могут допускать промахи так же, как и разработчики продукта

Гибкость конфигурации: можно встроить любые утилиты и адаптировать инструмент под свои задачи

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

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

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

Комментарии (2)


  1. 4ou4
    09.09.2025 12:52

    подскажите, как на анроид или йос через их терминалы скрипт - фото сделать и, главное, сохранить?

    Через а снимок нельзя сохранить, через й снимок можно только отправить по ip протоколу.

    Надо сделать n снимков, которые глупо и физически невозможно руками сделать. Не подскажите, где найти подобный скрипт? Сидеть и жать руками на экран весь день, это, простите, издевательство.

    Это идеальная задача для cli приложения, а не сидеть весь день строчить рукой по экрану, как обезьяна.

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

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


  1. vldmrmlkv
    09.09.2025 12:52

    del