Введение
Встраиваемое программное обеспечение (embedded software) управляет критически важными системами: от бытовой техники до медицинского оборудования и авионики. Ошибки в таком ПО могут приводить к:
- сбоям в работе устройств;
- потере данных;
- угрозе безопасности пользователей;
- финансовым потерям и репутационному ущербу.
Цель тестирования — выявить дефекты на ранних этапах, обеспечить:
- корректность функционирования;
- соответствие требованиям;
- устойчивость к внештатным ситуациям;
- предсказуемое поведение в реальном времени.
В статье разберём:
- специфику тестирования встраиваемых систем;
- уровни тестирования (модульное, интеграционное, системное);
- методы имитации аппаратуры;
- инструменты и практики;
- типичные проблемы и способы их решения.
1. Особенности встраиваемых систем и их влияние на тестирование
1.1. Ключевые ограничения
- Ресурсы:
- малый объём ОЗУ/ПЗУ;
- ограниченная вычислительная мощность;
- отсутствие виртуальной памяти.
- Аппаратная зависимость:
- прямой доступ к регистрам периферии;
- работа с прерываниями и DMA;
- жёсткие временные рамки (real‑time).
- Среда исполнения:
- отсутствие ОС или RTOS с ограниченными возможностями;
- взаимодействие с аналоговыми датчиками и актуаторами.
1.2. Вызовы для тестирования
- Невозможность прямого запуска тестов на целевой платформе (из‑за стоимости/доступности).
- Сложность наблюдения за внутренними состояниями.
- Воспроизводимость тестов при влиянии внешних факторов (температура, помехи).
- Отладка без привычных инструментов (отладчика, логов).
2. Уровни тестирования
2.1. Модульное тестирование (Unit Testing)
Цель: проверить отдельные функции/модули изолированно.
Объекты тестирования:
- алгоритмы обработки данных;
- драйверы периферии (абстрагированные);
- служебные утилиты (CRC, буферы).
Подходы:
- Тестирование «в обстановке разработки» (host‑based):
- запуск на ПК в среде Linux/Windows;
- использование фреймворков: Unity, CMock, Google Test.
- Тестирование на целевой платформе (target‑based):
- компиляция под МК;
- вывод результатов через UART/LED.
Пример (Unity для C):
#include "unity.h"
#include "math_utils.h"
void setUp(void) {}
void tearDown(void) {}
void test_sqrt_positive(void) {
TEST_ASSERT_EQUAL_FLOAT(2.0, sqrt_approx(4.0));
}
void test_sqrt_zero(void) {
TEST_ASSERT_EQUAL_FLOAT(0.0, sqrt_approx(0.0));
}
Лучшие практики:
- изоляция тестируемого кода от аппаратных зависимостей (через абстракции);
- использование моков (mocks) для имитации периферии;
- покрытие граничных случаев (переполнение, нулевые указатели).
2.2. Интеграционное тестирование (Integration Testing)
Цель: проверить взаимодействие модулей и подсистем.
Сценарии:
- обмен данными между драйвером и прикладным слоем;
- обработка прерываний в контексте задач RTOS;
- синхронизация через мьютексы/семафоры;
- передача пакетов между сетевыми уровнями.
Методы:
- Стенды с эмуляцией периферии (например, генератор сигналов вместо датчика).
- Программные симуляторы (QEMU, Renode).
- Аппаратные тестовые платы с контрольными точками.
Пример: тестирование модуля UART + протокол:
- отправка тестовых сообщений;
- проверка корректности CRC;
- имитация ошибок (кадр, переполнение буфера).
Критерии успеха:
- отсутствие взаимоблокировок;
- соблюдение таймингов;
- корректная обработка ошибок.
2.3. Системное тестирование (System Testing)
Цель: валидация всей системы в условиях, приближённых к реальным.
Проверяемые аспекты:
- выполнение требований спецификации;
- работа в реальном времени (jitter, latency);
- энергопотребление;
- устойчивость к помехам и перепадам питания.
Инструментарий:
- осциллографы для замера таймингов;
- логические анализаторы для протоколов;
- стенды с климатическими камерами.
3. Имитация аппаратуры (Hardware Emulation/Simulation)
3.1. Зачем нужна имитация?
- Доступность: целевые платы могут быть дорогими или ещё не готовы.
- Повторяемость: контроль входных воздействий (датчики, помехи).
- Безопасность: тестирование критических сценариев без риска для оборудования.
- Скорость: ускорение циклов разработки/тестирования.
3.2. Методы имитации
- Программные симуляторы:
- QEMU — эмуляция процессоров (ARM, RISC‑V).
- Renode (от SiFive) — симуляция целых систем с периферией.
- Simulink — моделирование аналоговых и цифровых систем.
- Фреймворки для мокирования:
- CMock (для C) — генерация моков на основе заголовков.
- Fake Function Framework (FFF) — простые фейки функций.
- Физические эмуляторы:
- FPGA‑платформы (например, Xilinx Zynq) для имитации ASIC.
- тестовые платы с переключаемыми нагрузками.
3.3. Пример: мокирование драйвера GPIO
Исходный код (gpio.h):
int gpio_read(pin_t pin);
void gpio_write(pin_t pin, int value);
Мок (сгенерированный CMock):
#include "cmock.h"
#include "gpio_mock.h"
int gpio_read_ExpectAndReturn(pin_t pin, int ret) {
mock_expect(&gpio_read_mock, (void*)&pin, sizeof(pin_t));
return ret;
}
Тест:
void test_led_control(void) {
gpio_write_Expect(LED_PIN, 1);
led_on();
TEST_ASSERT_TRUE(gpio_write_CalledOnce());
}
3.4. Ограничения имитации
- Точность таймингов: симулятор не воспроизводит реальные задержки.
- Аналоговые эффекты: помехи, джиттер, температурные зависимости.
- Ресурсы: сложные модели требуют мощных ПК.
4. Инструменты и инфраструктура тестирования
4.1. Фреймворки для C/C++
- Unity — лёгкий фреймворк для встраиваемых систем.
- CMock — генерация моков для Unity.
- Google Test — для host‑based тестирования.
- Ceedling — сборка (Rake) + тесты + моки.
4.2. Средства автоматизации
- CMake/Make — сборка тестовых бинарников.
- Jenkins/GitLab CI — непрерывная интеграция.
- Python‑скрипты — управление тестами и анализ результатов.
4.3. Аппаратные инструменты
- Логические анализаторы (Saleae Logic, Siglent):
- захват цифровых сигналов;
- декодирование протоколов (I²C, SPI, UART).
- Осциллографы (Keysight, Rigol):
- измерение напряжений и таймингов;
- поиск аномалий.
- JTAG/SWD‑отладчики (Segger J‑Link, ST‑Link):
- пошаговое выполнение;
- просмотр регистров.
5. Практики эффективного тестирования
5.1. Раннее тестирование (Shift‑Left)
- писать тесты до кода (Test‑Driven Development, TDD);
- проверять требования на тестируемость;
- использовать статический анализ (Cppcheck, SonarQube).
5.2. Покрытие кода
- измерять statement coverage, branch coverage;
- инструменты: gcov, lcov, VectorCAST.
- цель: ≥ 80 % для критичных модулей.
5.3. Тестирование в реальном времени
- замерять latency прерываний;
- проверять deadlines задач RTOS;
- имитировать перегрузку процессора.
5.4. Отчётность и прослеживаемость
- связывать тесты с требованиями (матрица трассировки);
- сохранять лог



