1. Тема дня

Как отделить прикладную логику от ESP-IDF, STM32 HAL и реального железа, чтобы запускать сотни тестов на Linux/Windows за секунды. Правильная цепочка:

text
чистая логика на ПК
→ unit-тесты компонентов на ESP32/STM32
→ интеграционные тесты
→ HIL на Raspberry Pi
→ тесты всей системы

Главная мысль: чем меньше модуль знает о GPIO, UART, FreeRTOS и HAL, тем проще доказать, что его логика работает правильно.

2. Зачем это нужно

Без платы можно тестировать:

text
EC25:
  AT-парсер, fragmented UART input, timeout, state machine.
Светофор:
  debounce, AC pulse window, RED/YELLOW/GREEN, conflict.
RS-485/CAN:
  CRC, codec, Modbus parser, CAN ID map.
Fault manager:
  классификация ошибок, retry limits, degraded mode.
Конфигурация:
  defaults, validation, migration, A/B выбор.

3. Теория

Host unit tests

Запускаются на ПК и должны быть быстрыми, детерминированными, без реального времени, без FreeRTOS-задач и без регистров MCU. Тестируем:

text
phase_detector_update()
at_parser_feed()
modbus_crc16()
config_validate()
fault_policy_decide()
mqtt_message_encode()

Target unit tests

Запускаются на ESP32/STM32 и проверяют уже FreeRTOS, allocator, hardware timers, GPIO loopback, UART loopback, DMA, порядок callback/interrupt. HIL

Проверяет физику: оптопары, EC25, I2C, CAN/RS-485, питание, watchdog, OTA rollback.

Паттерн core + adapter

Плохо:

c
void phase_detector_update(void)
{
    int red = gpio_get_level(RED_GPIO);
    int64_t now = esp_timer_get_time();
    xQueueSend(phase_queue, ...);
    ESP_LOGI(TAG, "Phase changed");
}

Лучше:

text
input_driver:
  GPIO/expander/ADC → raw_mask
phase_detector_core:
  raw_mask + timestamp → phase_event_t
phase_service:
  phase_event_t → FreeRTOS queue/MQTT/CAN

Паттерн step()

Бесконечную задачу тестировать неудобно:

c
void modem_task(void *arg)
{
    while (1) {
        modem_service_step(&s_modem, platform_time_us());
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

Теперь test вызывает modem_service_step() с виртуальным временем.

Время как аргумент

Плохо:

c
return esp_timer_get_time() - s_start_us > 5000000;

Лучше:

c
bool timeout_expired(int64_t now_us, int64_t start_us, int64_t timeout_us)
{
    return now_us - start_us >= timeout_us;
}

4. Типичные ошибки

  • Тестировать функцию вместе с HAL.
  • Использовать реальные задержки sleep(5).
  • Проверять внутренние индексы вместо поведения.
  • Тестировать только happy path.
  • Mock всегда возвращает успех.
  • Писать отдельную копию алгоритма в тесте.
  • Считать coverage доказательством качества.

5. Практическое задание

Создай host-compatible phase_detector_core. Интерфейс:

c
typedef enum {
    PHASE_NONE = 0,
    PHASE_RED,
    PHASE_YELLOW,
    PHASE_GREEN,
    PHASE_CONFLICT,
} phase_t;
typedef struct {
    bool initialized;
    uint8_t stable_mask;
    uint8_t candidate_mask;
    uint64_t candidate_since_us;
    uint32_t debounce_us;
    uint32_t sequence;
} phase_detector_t;
typedef struct {
    bool emitted;
    phase_t phase;
    uint8_t mask;
    uint32_t sequence;
    uint64_t timestamp_us;
} phase_event_t;

Тесты:

text
- RED emitted only after debounce.
- same stable phase is not emitted twice.
- short GREEN glitch does not replace RED.
- stable GREEN replaces RED.
- RED+GREEN is CONFLICT.

6. Что попробовать дальше

  • Добавить тесты для AT-парсера EC25.
  • Добавить config migration tests.
  • Добавить fault manager tests.
  • Подключить host tests в CI перед сборкой прошивки.

Задание

Проверьте debounce без sleep. Опишите последовательность raw masks и timestamps, доказывающую, что короткий GREEN glitch не заменяет устойчивый RED.

Критерии самопроверки: Использовать virtual time и публичное поведение; не опираться на internal indices и не дублировать алгоритм в тесте.

Показать ответ автора

Передать чистому detector явные timestamps и masks: установить устойчивый RED, внедрить GREEN короче настроенного debounce interval, затем вернуть RED. Проверить наблюдаемые phase events и отсутствие ложного GREEN event.

Задание

Отнесите AT parser test, UART DMA loopback test и modem power-loss test к host, target или HIL. Объясните, что каждый способен доказать.

Критерии самопроверки: Указать границу и ограничение каждого теста; host mock не доказывает физический signal timing и реальное modem recovery.

Показать ответ автора

AT parsing с переданными byte fragments — host test чистой логики parser. UART DMA loopback — target test peripheral/driver behavior. Modem power loss — HIL test оборудования и recovery integration.