
Update: Видео версия материала https://youtu.be/9ITrzCq-YKI
Занимательный факт. Исходный код программы бортового управляющего компьютера лунного модуля Аполлон 11 содержит 64830 строк. Исходные коды прошивок навигационного приемника, которые мы в МЭИ разрабатываем последние десять лет, содержат 217510 строчек на C++ и 181236 строчек на SystemVerilog. И я всё жду, когда это количество перейдет в качество.
Встраиваемый софт - это не только прошивки небольших контроллеров, он может быть объемным и сложным. Его разработка, например, для современных систем связи и навигации, может стоить дороже разработки схемотехники, корпуса или даже запуска в производство интегральных микросхем.
Если аппаратные решения определяют потенциал будущего изделия, то софт - длинная дорога для реализации этого потенциала. Выбор аппаратных решений осуществляется на первых этапах разработки устройства, а затем они слабо подвержены изменениям. Прошивки же эволюционируют пока устройством продолжают пользоваться.
Кодовая база переползает из проекта в проект, от изделия к изделию, многие из которых производятся и используются одновременно. Наработки и заплатки одного устройства нужны и в другом. Программное обеспечение - это технология. Технология развивается вместе с компанией и коллективом, применяется к нескольким продуктам одновременно. Требовать в каждом контракте разрабатывать прошивку с нуля, это как требовать от шеф-повара в каждом новом ресторане придумывать новые блюда. Он перепишет технологическую карту, но борщ останется борщом.
Мы оказываемся в ситуации, когда одни и те же программные модули влияют на несколько разных устройств. Да и в рамках одного устройства легко сломать одну фичу, добавляя другую. В процессе правок нужно контролировать выполнение требований, иначе говоря - тестировать.
Традиционные испытания радиоаппаратуры
Радиоаппаратура испытывалась, когда тестирование ПО ещё не было мейнстримом. Но типичные для России регламенты, например Система Разработки и Постановки Продукции на Производство, создавались давно и без должной оглядки на программную составляющую. Начертили блок-диаграммы на этапе разработки рабочей конструкторской документации, а затем проверили три раза: на приемо-сдаточных, предварительных и государственных испытаниях. Вот и все информационные технологии. Устройство на этих испытаниях проверяется досконально, но это немного не та частота, с который ты хочешь получать обратную связь при внесении изменений в код. Попробуйте исправить баг, которому семь месяцев к моменту его обнаружения!

Проводить испытания по полной программе значительно чаще - нереально. Типичная программа испытаний рассчитана на 1-2 недели ручного труда, нужно подключать/отключать разные приборы, использовать испытательные камеры и т.д. и т.п. Проверяется длинный список требований из следующих групп:

Даже если выделить специальных людей проверять эти требования в бесконечном цикле, то результат всё равно будет с запозданием, а исполнители очень скоро начнут сходить с ума. Нужно автоматизировать. А чтобы автоматизировать, нужно сократить программу испытаний.
Какие требования покрывать автотестами?
Так ли много требований могут сломаться при изменениях в прошивке? Пожалуй, в основном это требования назначения и, иногда, требования радиоэлектронной защиты. Их, а ещё ряд проверок выполнения требований к ПО, мы и автоматизировали:
требования назначения (правильное определение координат пользователя, быстрое решение, выдача наблюдений по спутникам, выдача навигационного сообщения, ...)
требования радиоэлектронной защиты (внеполосные излучения, устойчивость к помехам, ...)
требования к ПО (компилируемость, синтезируемость, соответствие заданным интерфейсам, запуск на целевой платформе, ...)
Может показаться, что это существенное сокращение программы испытаний, но на самом деле покрытие софтовых проблем всё ещё отличное:
самая частая ошибка при разработке ПО - код не компилируется или не синтезируется (забыли файл, не те ключи у разработчика, битстрим собирается с критическим ворнингом, прошивка перестала вмещаться в небольшую ПЛИС и т.д. и т.п.);
требования назначения доминируют в программе и методике испытаний радиоустройств (например, это 73% ПМИ в нашей последней разработке);
в навигации факт выдачи координат пользователя с точностью в 1 метр - это маленькое чудо, должны сойтись все-все звезды; если кто-то где-то что-то сломал, решения навигационной задачи скорее всего не будет, интеграционный/системный тест лучше и не пожелаешь.
Какие компоненты тестировать?
Как следует из списка проверяемых требований, объектом тестирования выступает устройство и компоненты его ПО. Но какое устройство и какие компоненты? Можно создать стенды под разные устройства и тестировать все подряд. Но автотесты не бесплатны в разработке и поддержке, поэтому нужно фокусироваться на главном.
В качестве устройства мы выделили изделие с максимумом функций и интерфейсов - навигационный приемопередатчик. Он не только принимает сигналы GPS, ГЛОНАСС и т.д., но может и формировать навигационные сигналы для разных нужд. Например, для испытания другой аппаратуры или построения локальных навигационных систем.

В комплекс его ПО входит множество компонентов:

Но наиболее критичными являются прошивка процессорной системы (Firmware в виде демона для запуска на ОС), прошивка программируемой логики (Logic Modules, из которых генерируется прошивка ПЛИС) и хостовые программы для взаимодействия с устройством.
Кроме того, у нас есть симуляция для PC, которая позволяет обернуть процессорную прошивку, и она будет считать, что запущена прямо на устройстве. Она не требует конечного устройства, не требует лабораторных приборов, выполняется во много раз быстрее реального времени. Звучит как ��тличный объект для быстрой проверки выполнения основных требований назначения. Почти бесплатно.
Как часто тестировать?
За один месяц разработки наша небольшая команда успевает накидать около 50 изменений (мердж реквестов), а количество промежуточных пушей далеко за тысячу.
Замечательно было бы реагировать на каждое изменение, вносимое разработчиками в код, в том числе в ветках. Тогда разработчики быстрее узнают о проблемах и быстрее их исправят. Но, к сожалению, пуши от разработчиков поступают чаще, чем длительность исполнения некоторых тестов. Так прошивка для ПЛИС может разводиться несколько часов, а тест на живом приемнике может потребовать сбора данных на нескольких десятках минут и т.д. Можно было бы размножать ресурсы для выполнения тестов, но вместо этого мы дополнительно сократили программу испытаний для отдельных пушей.

Второе по частоте событие - мердж реквест. Перед тем, как вносить изменения в основную ветку, их надо хорошо протестировать. Раньше это требовало час ручной работы проверяющего, теперь же эти тесты делает машина.
Третий тип событий - ночные тесты. Тут оборудование свободно. Можно занять несколько часов машинного времени, решить задачи оптимизации, подготовить прошивки ПЛИС с полным заполнением кристалла и т.д. и т.п. Подготовить прошивки для использования конечным пользователем.

Три разных типа событий, три разных программы, отличающиеся объемом и временем исполнения. Но все они - автоматические. Вместе они позволяют реализовать систему непрерывного применения изменений (Continuous Integration, CI) и непрерывной поставки изменений пользователю (Continuous Deploy, CD)
Использование Gitlab CI/CD для организации автотестов
Первые три попытки
Немного истории. Как опытные разработчики велосипедов, мы начали с собственного решения. Написали библиотечку для общения с лабораторными приборами в матлабе, скрипты тестирования под разные требования. Разложили по папочкам. Написали описание к каждому тесту в Word'е. И запускали эти скрипты по cron'у через Makefile. Отчет приходил ночью на почту.

Эта система ушла в забвение в 2014 после обновления сервера, проработав пару месяцев. Затем авторазложение повторилось в 2016 году, после того, как кто-то утащил имитатор сигналов для выполнения другой работы. Она не преодолела порог полезности - слишком много сил на поддержание, результат приходит с задержкой, не изящно и не красиво.

Третья версия была оформлена в виде тестов на Check, запускаемых при каждой компиляции на машине разработчика. Но в итоге в этой системе выжили только юнит-тесты, для которых Check изначально и разработан. Тесты на аппаратуре так не проведешь, а интеграционные и симуляция занимают слишком много времени, разработчики их отключали.
В четвертый раз инструмент соответствовал задаче. В качестве системы контроля версий - git, ведение проекта в Gitlab, а система автотестирования в Gitlab CI/CD. Ну и ряд организационных мер: ответственный человек и выделенные исключительно под тестирование лабораторные приборы и серверы.

Ядром системы выступает локальный Gitlab-сервер. На нем хранятся репозитории, с ним взаимодействуют разработчики и раннеры. Раннеры - специальные программы, запущенные на отдельных серверах. Они периодически обращаются на сервер с вопросом: а есть что потестировать? Если есть, они получают задачу и начинают её выполнять. Сейчас мы используем три раннера: один для сборки прошивок ПЛИС, один для взаимодействия с изделием и лабораторными приборами, и один для остальных нужд.
При необходимости, число раннеров может быть легко увеличено. Например, мы экспериментировали с покупкой серверных мощностей на несколько часов. Вы можете развернуть сотню-другую раннеров, набрать необходимую статистику или решить задачу оптимизации параметров прошивки ПЛИС, а затем отключить их.
Этапы испытаний
Сущности и понятия, используемые в Gitlab, хорошо ложатся на термины отечественных ГОСТов. Что ГОСТу этап, то Gitlab'у stage, что ГОСТу программа испытаний, то Gitlab'у pipeline (конвейер). Мы разбили программу испытаний на три этапа:
Build - Сборка - на этом этапе компилируются программы, генерируются прошивки ПЛИС, выполняются юнит-тесты
Sim - Симуляция - проверяется выполнение требований назначения в симуляционном окружении, когда мы прошивку процессорной системы оборачиваем кодом, имитирующим исполнение на целевом устройстве
Testbed - Стенд - собранные компоненты программного комплекса прошиваются в устройство, оно проверяется на выполнение требований назначения и требований радиоэлектронной защиты; активно применяются лабораторные приборы

Есть ещё чисто технический четвертый этап, Conclusion, на нем мы собираем итоговые документы о проведенном тестировании.
Проверки
По ГОСТу этап содержит проверки или испытания, а в Gitlab'е этап содержит job'ы. Один job - одна проверка.
Каждая проверка - это скрипт, запускаемый на одном из раннеров в нужном docker-окружении. Ему могут быть предоставлены результаты выполнения других проверок (например, собранные прошивки). А ещё ему доступна локальная копия всего репозитория, все исходные коды проекта.

Правила запуска скрипта описываются в gitlab-ci.yaml файле, располагаемом в корне репозитория. Скрипты теста также лежат в репозитории. Таким образом, вы всегда можете вернуться к нужному коммиту и перезапустить проверки, если это потребуется.
Пример описания одной из проверок в gitlab-ci.yaml
Разделы с описанием проверок в gitlab-ci.yaml формируются отдельным генератором под составленную программу испытаний, выглядит они примерно так:
adicusSolveslnTTFFforGPS:
extends:
- .testbed
- .artifacts_settings
allow_failure: true
rules:
- if: '$CI_PIPELINE_SOURCE == "push"'
when: always
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: always
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- when: never
variables:
PREFIX: '016'
needs:
- testBedlsPowered
- trcvdlsCompilableForPetaLinux
- simlsCompilableForHostLinux
script: >
python3 ./qa/tests/is_testbed_ttff_low.py --param '{"system": ["GPS"], "ttff": 300, "IP": ["192.168.0.117"], "DEV": ["CLONICUS"], "accumulation_time":150, "PLTYPEFINDBIT" : "CI" }' --obj "Прошивка процессорной системы" --prefix $PREFIX --stage $CI_JOB_STAGE --CI_JOB_NAME $CI_JOB_NAME --GITLAB_TOKEN $GITLAB_TOKEN
.artifacts_settings:
artifacts:
when: always
paths:
- qa/reports/$CI_JOB_NAME
- qa/reports/log
- built
- built/petalinux
- built/qnx
- qa/built
- qa/built/petalinux
- qa/built/qnx
- qa/log
expire_in: 2 week
.testbed:
image: navsyslab/sim:v3.3
stage: testbed
cache:
key: $CI_COMMIT_SHORT_SHA
paths:
- qa/cache
- qa/log
- built
- built/petalinux
- built/qnx
tags:
- testbed
resource_group: testbed
before_script:
- mkdir -p ./qa/cache
after_script:
- ls -l qa/cacheЗапуск в docker-окружении позволяет отвязаться от конкретного сервера, на котором будут выполняться проверки (за исключением тестов с аппаратурой). Но самое главное, это позволяет отвязаться от компьютера разработчика и его локальных настроек. Появляется общий знаменатель для настроек компиляторов, синтезаторов и т.д. для всей команды - контейнер.
Сердце каждого тестового скрипта - последовательность шагов, с помощью которых осуществляется проверка. Каждый шаг содержит три блока:
запись описания предстоящих на этом шаге действий в файл методики испытаний method.tex
непосредственно выполнение действия и получение результата
запись результата в файл протокола protocol.tex

Благодаря такой простой организации тестового скрипта описание методики, действия и протокол не расходятся и не содержат воду.
Приложения
При разработке традиционных методик испытаний по ГОСТ инженеры не брезгуют выносить повторяющиеся куски испытаний в Приложения к документу. Обычно там скапливаются схемы установок, процедуры настройки лабораторных приборов, процедуры набора статистики и т.д.
В системе автоматического тестирования мы Приложения выделили в отдельные скрипты - apps. Они нужны для того, чтобы:
не дублировать код в скриптах проверок
не дублировать действие, если его результат известен из другой проверки
Половина проверок на требования назначения начинаются с одних и тех же действий: настроить имитатор сигналов на определенную систему, запустить приемник, набрать пять минут логов. А далее проверки отличаются только способом обработки этого лога. Скрипт-приложение позволяет не ждать эти пять минут во второй и последующих проверках, а сразу перейти к обработке лога.

Скрипт-приложение кэширует результат своей работы в отдельной директории для каждого набора входных параметров. Если какая-то проверка обращается к нему, он сначала проверяет, а нет ли у него готового ответа. Если есть, достает из кэша. Если нет, выполняет действия и отдает результат.
Стенд и взаимодействие с лабораторными приборами
В составе стенда мы собрали лабораторные приборы одного производителя - немецкого Rohde&Schwarz:
векторный генератор сигналов с функциями имитации навигационных сигналов SMBV100A,
осциллограф RTM1054,
анализатор спектра FSV3.

Векторный генератор используется для имитации выхода ГНСС антенны: навигационных сигналов различных спутников, теплового шума усилителя, помех.
Осциллограф позволяет контролировать точность выдачи приемником метки времени. Она сравнивается с секундной меткой от имитатора сигналов. Контролируется наличие метки и разница моментов прихода меток с субнаносекундной точностью.
Анализатор спектра позволяет оценить чистоту спектра формируемых сигналов, выполнение требований к внеполосным излучениям, уровень мощности и пик-фактор.
Все приборы управляются по локальной сети посредством SCPI команд. Для этого на пайтоне была написана библиотека функций для настройки приборов под сценарии проводимых испытаний, а также получения с них измерений.
Например, так генератор настраивается на имитацию шума ГНСС антенны
def setawgn(self):
self.sendcommand('SOUR:AWGN:STAT OFF')
self.sendcommand('SOUR:AWGN:MODE ADD')
self.sendcommand('SOUR:AWGN:BWID 75e6')
self.sendcommand('SOUR:AWGN:BWID:RAT 1.0')
self.sendcommand('SOUR:AWGN:DISP:MODE RF')
self.sendcommand('SOUR:AWGN:POW:MODE CN')
self.sendcommand('SOUR:AWGN:POW:RMOD NOISE')
self.sendcommand('SOUR:AWGN:CNR -26')
self.sendcommand('SOUR:AWGN:POW:NOISE -44')
self.sendcommand('SOUR:AWGN:BRAT 1023000')
self.opc()
self.sendcommand('SOUR:AWGN:STAT ON')
self.opc()
print(f'SMBV:AWGN set to ADD mode')Что-то даже есть в сети.
Функции библиотеки вызываются либо непосредственно из Проверок, либо из Приложений.
Оформление отчета по ЕСКД
На этапе Conclusion отдельные методики и протоколы собираются в два документа:
Программа и методика испытаний
Книга протоколов испытаний

Благодаря LaTeX и нашему шаблону они выглядят на радость нормоконтролеру:

Для этого на этапе проверок и приложений готовятся уникальные tex-файлы и изображения. На последнем этапе они конвертируются в pdf.
Непрерывная интеграция изменений
Результаты испытаний хранятся на сервере и доступны участникам проекта. Мы храним их 120 дней, после чего они автоматически удаляются.

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

Когда разработчик выполнил поставленную задачу, он заводит Merge Request для передачи изменений в основную ветку проекта. Успешно выполненные тесты - это необходимое условие для принятия Merge Request'а. Если код и изделие не прошли испытания, изменения не попадут в основную ветку проекта.
Изменения, продержавшиеся в основной ветке до ночи и прошедшие ночные тесты, становятся рабочими для пользователей. На следующий день они получат их из артефактов соответствующего конвейера.
Заключение
Автотесты при разработке сложных радиоэлектронных устройств не только возможны, но и необходимы. Они существенно повышают качество продукта и снимают нагрузку с ключевых разработчиков, позволяют быть уверенным в результате и устранять проблемы сразу после их появления.
