- Модуль bsp/led
- Шаблон хост-тестов для модуля bsp/led
This commit is contained in:
Dmitry Akimov 2026-03-16 14:34:05 +03:00
parent 33aabbcce1
commit 72045dabb3
24 changed files with 1186 additions and 1065 deletions

View file

@ -51,7 +51,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
apt-get update && apt-get install -y --no-install-recommends \ apt-get update && apt-get install -y --no-install-recommends \
curl git cmake ninja-build bear \ curl git cmake ninja-build bear \
python3 python3-pip \ python3 python3-pip \
clang-17 clangd-17 clang-tidy-17 clang-format-17 \ clang-17 clangd-17 libclang-rt-17-dev clang-tidy-17 clang-format-17 \
gdb-multiarch \ gdb-multiarch \
build-essential \ build-essential \
&& update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-17 100 \ && update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-17 100 \

View file

@ -128,8 +128,7 @@
"displayName": "Host Tests — Debug", "displayName": "Host Tests — Debug",
"configurePreset": "host-debug", "configurePreset": "host-debug",
"targets": [ "targets": [
"test_host_simple", "test_bsp_led"
"test_host_second"
] ]
}, },
{ {
@ -137,8 +136,7 @@
"displayName": "Host Tests — Release", "displayName": "Host Tests — Release",
"configurePreset": "host-release", "configurePreset": "host-release",
"targets": [ "targets": [
"test_host_simple", "test_bsp_led"
"test_host_second"
] ]
} }
], ],

View file

@ -11,6 +11,8 @@ add_library(
generated/board.c generated/pin_mux.c generated/clock_config.c generated/board.c generated/pin_mux.c generated/clock_config.c
generated/startup/startup_MIMXRT1052.S) generated/startup/startup_MIMXRT1052.S)
add_subdirectory(led)
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки # Подавляем предупреждения при компиляции собственных .c файлов библиотеки
target_compile_options(bsp_board PRIVATE -w) target_compile_options(bsp_board PRIVATE -w)
@ -23,24 +25,19 @@ target_compile_definitions(
bsp_board PUBLIC CPU_MIMXRT1052CVJ5B SKIP_SYSCLK_INIT XIP_EXTERNAL_FLASH=1 bsp_board PUBLIC CPU_MIMXRT1052CVJ5B SKIP_SYSCLK_INIT XIP_EXTERNAL_FLASH=1
XIP_BOOT_HEADER_ENABLE=1 XIP_BOOT_HEADER_DCD_ENABLE=1) XIP_BOOT_HEADER_ENABLE=1 XIP_BOOT_HEADER_DCD_ENABLE=1)
# Сценарий A: XIP — код исполняется прямо из Flash
add_library(bsp_boot_xip INTERFACE)
target_compile_definitions(
bsp_boot_xip INTERFACE XIP_EXTERNAL_FLASH=1 XIP_BOOT_HEADER_ENABLE=1
XIP_BOOT_HEADER_DCD_ENABLE=1)
# Сценарий B: копирование в ITCM (для bootloader или критичного кода)
add_library(bsp_boot_itcm INTERFACE)
target_compile_definitions(
bsp_boot_itcm INTERFACE XIP_BOOT_HEADER_ENABLE=1 XIP_BOOT_HEADER_DCD_ENABLE=1
# XIP_EXTERNAL_FLASH намеренно отсутствует
)
# Сценарий C: копирование в SDRAM (для app с графикой)
add_library(bsp_boot_sdram INTERFACE)
target_compile_definitions(
bsp_boot_sdram INTERFACE XIP_BOOT_HEADER_ENABLE=1
XIP_BOOT_HEADER_DCD_ENABLE=1)
# sdk_device PUBLIC — транзитивно подтянется во все firmware таргеты # sdk_device PUBLIC — транзитивно подтянется во все firmware таргеты
target_link_libraries(bsp_board PUBLIC sdk_device sdk_clock sdk_common) target_link_libraries(bsp_board PUBLIC sdk_device sdk_clock sdk_common)
# # Сценарий A: XIP — код исполняется прямо из Flash add_library(bsp_boot_xip
# INTERFACE) target_compile_definitions( bsp_boot_xip INTERFACE
# XIP_EXTERNAL_FLASH=1 XIP_BOOT_HEADER_ENABLE=1 XIP_BOOT_HEADER_DCD_ENABLE=1)
# # Сценарий B: копирование в ITCM (для bootloader или критичного кода)
# add_library(bsp_boot_itcm INTERFACE) target_compile_definitions( bsp_boot_itcm
# INTERFACE XIP_BOOT_HEADER_ENABLE=1 XIP_BOOT_HEADER_DCD_ENABLE=1 #
# XIP_EXTERNAL_FLASH намеренно отсутствует )
# # Сценарий C: копирование в SDRAM (для app с графикой)
# add_library(bsp_boot_sdram INTERFACE) target_compile_definitions(
# bsp_boot_sdram INTERFACE XIP_BOOT_HEADER_ENABLE=1
# XIP_BOOT_HEADER_DCD_ENABLE=1)

View file

@ -1,8 +1,10 @@
#include "board.h" #include "board.h"
#include "clock_config.h" #include "clock_config.h"
#include "pin_mux.h" #include "pin_mux.h"
void BOARD_Init(void) { void board_hw_init(void)
{
BOARD_InitPins(); BOARD_InitPins();
BOARD_BootClockRUN(); BOARD_BootClockRUN();
} }

View file

@ -4,7 +4,7 @@
/*! @brief The board name */ /*! @brief The board name */
#define BOARD_NAME "TFT4.3_UB_BOARD" #define BOARD_NAME "TFT4.3_UB_BOARD"
void BOARD_Init(void); /*! //TODO: @brief The board name */
void board_hw_init(void);
#endif /* _BOARD_H_ */ #endif /* _BOARD_H_ */

17
bsp/led/CMakeLists.txt Normal file
View file

@ -0,0 +1,17 @@
if(BUILD_TESTS_HOST)
return()
endif()
add_library(bsp_led STATIC src/led.c)
target_include_directories(
bsp_led
PUBLIC include/ # led.h доступен снаружи
PRIVATE src/ # ничего приватного сейчас, но структура готова
)
target_link_libraries(
bsp_led
PUBLIC bsp_board # board.h, pin_mux.h — транзитивно
PRIVATE sdk_gpio # fsl_gpio.h — не торчит наружу
)

70
bsp/led/README.md Normal file
View file

@ -0,0 +1,70 @@
# bsp_led
Драйвер двух пользовательских светодиодов на плате.
---
## Аппаратная часть
| `led_id_t` | Сигнал | GPIO | Pin | Координата | Активный уровень |
|-----------------|------------|-------|-----|------------|-----------------|
| `LED_HEARTBEAT` | `UserLed1` | GPIO3 | 3 | M4 | LOW (0 = горит) |
| `LED_APP` | `UserLed2` | GPIO3 | 4 | P2 | LOW (0 = горит) |
Пины сконфигурированы в `generated/pin_mux.h` (MCUXpresso Config Tools).
`INIT_GPIO_VALUE = 1U`оба LED выключены сразу после `led_init()`.
---
## API
```c
void led_init(void); // вызвать один раз после BOARD_InitPins()
void led_on(led_id_t id);
void led_off(led_id_t id);
void led_toggle(led_id_t id);
void led_set(led_id_t id, bool on);
bool led_get(led_id_t id);
```
---
## Использование
```c
#include "bsp/led.h"
// инициализация
led_init();
// heartbeat
led_toggle(LED_HEARTBEAT);
// прикладная индикация
led_on(LED_APP); // пакет принят / тест запущен
led_off(LED_APP); // сброс
```
---
## CMake
```cmake
target_link_libraries(<target> PRIVATE bsp_led)
```
Зависимости: `bsp_board` (PUBLIC, транзитивно), `sdk_gpio` (PRIVATE).
При `BUILD_TESTS_HOST=ON` компонент не собирается — мокается через `fff` на уровне теста.
---
## Файлы
```bash
led/
├── CMakeLists.txt
├── include/led.h # публичный API — без NXP хедеров
├── src/led.c # реализация, fsl_gpio.h только здесь
└── README.md # этот файл
```

49
bsp/led/include/bsp/led.h Normal file
View file

@ -0,0 +1,49 @@
#pragma once
/**
* @file led.h
* @brief BSP LED driver два светодиода на плате (active LOW)
*
* LED_HEARTBEAT системный, мигает как признак жизни прошивки
* LED_APP прикладной, управляется из firmware по ситуации
*
* Пины сконфигурированы в generated/pin_mux. Этот хедер не знает
* ни про GPIO-порты, ни про NXP SDK.
*/
#include <stdbool.h>
/* ── Идентификаторы светодиодов ─────────────────────────────────────── */
typedef enum
{
LED_HEARTBEAT = 0, /**< системный heartbeat */
LED_APP = 1, /**< прикладной индикатор */
} led_id_t;
/* ── API ────────────────────────────────────────────────────────────── */
/**
* @brief Инициализация обоих светодиодов.
* Вызвать один раз после BOARD_InitPins().
* После вызова оба LED выключены.
*/
void led_init(void);
/** @brief Включить светодиод. */
void led_on(led_id_t led_id);
/** @brief Выключить светодиод. */
void led_off(led_id_t led_id);
/** @brief Переключить состояние светодиода. */
void led_toggle(led_id_t led_id);
/**
* @brief Установить состояние светодиода явно.
* @param on true включить, false выключить
*/
void led_set(led_id_t led_id, bool is_enabled);
/** @brief Получить текущее состояние (true — горит). */
bool led_get(led_id_t led_id);

101
bsp/led/src/led.c Normal file
View file

@ -0,0 +1,101 @@
#include "bsp/led.h"
#include "fsl_gpio.h"
#include "pin_mux.h"
#include <stddef.h>
/**
* Пины взяты из generated/pin_mux.h имена макросов должны
* совпадать с тем что сгенерировал MCUXpresso Config Tools.
*
* Пины из generated/pin_mux.h (MCUXpresso Config Tools):
*
* UserLed1 GPIO3 pin 3 (coord M4, GPIO_SD_B1_03) LED_HEARTBEAT
* UserLed2 GPIO3 pin 4 (coord P2, GPIO_SD_B1_04) LED_APP
*
* INIT_GPIO_VALUE = 1U active LOW подтверждён схемой.
*/
/* ── Дескриптор одного светодиода ───────────────────────────────────── */
typedef struct
{
GPIO_Type *gpio;
uint32_t pin;
bool state; /**< true = горит (логическое состояние) */
} led_desc_t;
/* ── Таблица светодиодов ─────────────────────────────────────────────
* Порядок должен совпадать с led_id_t.
* */
static led_desc_t s_leds[] = {
[LED_HEARTBEAT] = {
.gpio = BOARD_INITPINS_UserLed1_GPIO,
.pin = BOARD_INITPINS_UserLed1_GPIO_PIN,
.state = false,
},
[LED_APP] = {
.gpio = BOARD_INITPINS_UserLed2_GPIO,
.pin = BOARD_INITPINS_UserLed2_GPIO_PIN,
.state = false,
},
};
#define LED_COUNT (sizeof(s_leds) / sizeof(s_leds[0]))
/* ── Приватные хелперы ───────────────────────────────────────────────── */
/** Перевести логическое состояние в физический уровень GPIO (active LOW). */
static inline uint8_t led_to_gpio_level(bool is_enabled)
{
return is_enabled ? 0U : 1U;
}
static inline void led_apply(led_desc_t *p_led)
{
GPIO_PinWrite(p_led->gpio, p_led->pin, led_to_gpio_level(p_led->state));
}
/* ── Публичный API ───────────────────────────────────────────────────── */
void led_init(void)
{
gpio_pin_config_t cfg = {
.direction = kGPIO_DigitalOutput,
.outputLogic = 1U, /* active LOW → начинаем с 1 = LED выключен */
.interruptMode = kGPIO_NoIntmode,
};
for (size_t i = 0; i < LED_COUNT; i++)
{
GPIO_PinInit(s_leds[i].gpio, s_leds[i].pin, &cfg);
s_leds[i].state = false;
}
}
void led_on(led_id_t led_id)
{
led_set(led_id, true);
}
void led_off(led_id_t led_id)
{
led_set(led_id, false);
}
void led_toggle(led_id_t led_id)
{
led_set(led_id, !s_leds[led_id].state);
}
void led_set(led_id_t led_id, bool is_enabled)
{
s_leds[led_id].state = is_enabled;
led_apply(&s_leds[led_id]);
}
bool led_get(led_id_t led_id)
{
return s_leds[led_id].state;
}

View file

View file

@ -12,13 +12,7 @@ target_sources(firmware_test
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
target_link_libraries( target_link_libraries(
firmware_test firmware_test
PRIVATE bsp_board PRIVATE bsp_board bsp_led
# SDK драйверы периферии — добавляй по мере написания тестов
sdk_lpuart # UART тест
sdk_flexcan # CAN тест
sdk_gpio # GPIO тест (кнопки, светодиоды, входы)
sdk_usdhc # uSD тест
sdk_semc # SDRAM тест
# Опциональные библиотеки # Опциональные библиотеки
lib_external # SEGGER RTT если включён через SEGGER_RTT_ENABLED lib_external # SEGGER RTT если включён через SEGGER_RTT_ENABLED
) )

View file

@ -1,36 +1,13 @@
/*
* Copyright (c) 2013 - 2015, Freescale Semiconductor, Inc.
* Copyright 2016-2017, 2024 NXP
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "board.h" #include "board.h"
#include "fsl_gpio.h" #include "bsp/led.h"
#include "pin_mux.h"
/*******************************************************************************
* Definitions
******************************************************************************/
/*******************************************************************************
* Prototypes
******************************************************************************/
/*******************************************************************************
* Variables
******************************************************************************/
/*******************************************************************************
* Code
******************************************************************************/
int main(void) int main(void)
{ {
BOARD_Init(); board_hw_init();
GPIO_PinWrite(BOARD_INITPINS_UserLed1_PORT, BOARD_INITPINS_UserLed1_PIN, 0); led_init();
// GPIO_PinWrite(BOARD_INITPINS_UserLed2_PORT, BOARD_INITPINS_UserLed2_PIN, 0);
led_on(LED_HEARTBEAT);
led_off(LED_APP);
while (1) while (1)
{ {
} }

View file

@ -31,7 +31,7 @@ endif()
# === FFF Mocking Framework === # === FFF Mocking Framework ===
if(UNITY_TESTING_ENABLED) if(UNITY_TESTING_ENABLED)
add_library(fff INTERFACE) add_library(fff INTERFACE)
target_include_directories(fff INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/fff) target_include_directories(fff INTERFACE fff)
target_link_libraries(lib_external INTERFACE fff) target_link_libraries(lib_external INTERFACE fff)
message(STATUS "FFF ✅") message(STATUS "FFF ✅")
else() else()

View file

@ -1,618 +0,0 @@
# Юнит тестирование: Unity + FFF
## Содержание
- [Юнит тестирование: Unity + FFF](#юнит-тестирование-unity--fff)
- [Содержание](#содержание)
- [1. Архитектура тестирования](#1-архитектура-тестирования)
- [2. Структура тестового файла](#2-структура-тестового-файла)
- [3. Unity — assertion API](#3-unity--assertion-api)
- [Целые числа](#целые-числа)
- [Числа с плавающей точкой](#числа-с-плавающей-точкой)
- [Булевые значения](#булевые-значения)
- [Указатели](#указатели)
- [Строки и массивы](#строки-и-массивы)
- [Диапазоны](#диапазоны)
- [Явный провал / пропуск](#явный-провал--пропуск)
- [4. FFF — создание фейков](#4-fff--создание-фейков)
- [Макросы объявления](#макросы-объявления)
- [Где объявлять](#где-объявлять)
- [5. FFF — управление поведением](#5-fff--управление-поведением)
- [Задать возвращаемое значение](#задать-возвращаемое-значение)
- [Подставить кастомную реализацию](#подставить-кастомную-реализацию)
- [Захват аргументов через custom\_fake](#захват-аргументов-через-custom_fake)
- [6. FFF — проверка вызовов](#6-fff--проверка-вызовов)
- [Счётчики и аргументы последнего вызова](#счётчики-и-аргументы-последнего-вызова)
- [История всех вызовов](#история-всех-вызовов)
- [Порядок вызовов разных функций](#порядок-вызовов-разных-функций)
- [Функция не была вызвана](#функция-не-была-вызвана)
- [7. Паттерны работы с HAL](#7-паттерны-работы-с-hal)
- [Паттерн: заглушка HAL для host-сборки](#паттерн-заглушка-hal-для-host-сборки)
- [Паттерн: мок таймера](#паттерн-мок-таймера)
- [Паттерн: тест конечного автомата (FSM)](#паттерн-тест-конечного-автомата-fsm)
- [8. Организация фейков в проекте](#8-организация-фейков-в-проекте)
- [9. setUp / tearDown — правильный сброс](#9-setup--teardown--правильный-сброс)
- [10. Ограничения и обходные пути](#10-ограничения-и-обходные-пути)
- [`static` функции](#static-функции)
- [Настройка лимитов истории](#настройка-лимитов-истории)
- [Глобальные хендлеры HAL](#глобальные-хендлеры-hal)
- [11. CMakeLists.txt для host-тестов](#11-cmakeliststxt-для-host-тестов)
- [12. Запуск тестов](#12-запуск-тестов)
- [Пример вывода при успехе](#пример-вывода-при-успехе)
- [Пример вывода при провале](#пример-вывода-при-провале)
---
## 1. Архитектура тестирования
В bare-metal проекте тестируемый код вызывает HAL-функции, которые недоступны на хосте.
FFF подменяет эти вызовы фейками, Unity проверяет результаты.
```
┌─────────────────────────────────────────────┐
│ Host Test Runner │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Unity │ │ FFF │ │
│ │ (assert) │ │ (fakes) │ │
│ └──────────┘ └──────────┘ │
│ ↓ ↓ │
│ ┌─────────────────────────────┐ │
│ │ Тестируемый код │ │
│ │ App/Src/led.c │ │
│ └─────────────────────────────┘ │
│ ↓ вызывает │
│ ┌─────────────────────────────┐ │
│ │ HAL Fakes (вместо реального│ │
│ │ stm32f4xx_hal.h) │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────────────┘
```
**Принцип seam (шов):** тестируемый код не знает, что вызывает фейк, а не реальный HAL — линковщик подставляет нужную реализацию в зависимости от сборки.
---
## 2. Структура тестового файла
```c
// Tests/host/test_led.c
#include "fff.h" // мок-фреймворк
#include "unity.h" // assertion фреймворк
#include "hal_fakes.h" // объявления фейков HAL
#include "led.h" // тестируемый модуль
// Обязательно — ровно один раз на весь тестовый файл
DEFINE_FFF_GLOBALS;
// ─── Жизненный цикл теста ─────────────────────────────────────────────────
void setUp(void)
{
// Сбрасываем фейки перед каждым тестом
RESET_FAKE(HAL_GPIO_WritePin);
RESET_FAKE(HAL_GetTick);
FFF_RESET_HISTORY();
}
void tearDown(void) {}
// ─── Тесты ────────────────────────────────────────────────────────────────
void test_led_on_sets_gpio_high(void)
{
led_set(LED_ON);
TEST_ASSERT_EQUAL(1, HAL_GPIO_WritePin_fake.call_count);
TEST_ASSERT_EQUAL(GPIO_PIN_SET, HAL_GPIO_WritePin_fake.arg2_val);
}
void test_led_off_sets_gpio_low(void)
{
led_set(LED_OFF);
TEST_ASSERT_EQUAL(GPIO_PIN_RESET, HAL_GPIO_WritePin_fake.arg2_val);
}
// ─── Runner ───────────────────────────────────────────────────────────────
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_led_on_sets_gpio_high);
RUN_TEST(test_led_off_sets_gpio_low);
return UNITY_END();
}
```
---
## 3. Unity — assertion API
### Целые числа
```c
TEST_ASSERT_EQUAL(expected, actual)
TEST_ASSERT_EQUAL_INT(expected, actual)
TEST_ASSERT_EQUAL_INT8(expected, actual)
TEST_ASSERT_EQUAL_INT16(expected, actual)
TEST_ASSERT_EQUAL_INT32(expected, actual)
TEST_ASSERT_EQUAL_UINT8(expected, actual)
TEST_ASSERT_EQUAL_UINT32(expected, actual)
TEST_ASSERT_NOT_EQUAL(expected, actual)
```
### Числа с плавающей точкой
```c
TEST_ASSERT_EQUAL_FLOAT(expected, actual)
TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual) // |actual - expected| < delta
TEST_ASSERT_EQUAL_DOUBLE(expected, actual)
```
### Булевые значения
```c
TEST_ASSERT_TRUE(condition)
TEST_ASSERT_FALSE(condition)
TEST_ASSERT_NULL(pointer)
TEST_ASSERT_NOT_NULL(pointer)
```
### Указатели
```c
TEST_ASSERT_EQUAL_PTR(expected, actual)
```
### Строки и массивы
```c
TEST_ASSERT_EQUAL_STRING(expected, actual)
TEST_ASSERT_EQUAL_MEMORY(expected, actual, len) // побайтовое сравнение
TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, len) // сравнение массивов int
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, len)
```
### Диапазоны
```c
TEST_ASSERT_INT_WITHIN(delta, expected, actual)
TEST_ASSERT_UINT32_WITHIN(delta, expected, actual)
```
### Явный провал / пропуск
```c
TEST_FAIL() // тест падает безусловно
TEST_FAIL_MESSAGE("причина") // с сообщением
TEST_IGNORE() // тест пропускается (помечается как ignored)
TEST_IGNORE_MESSAGE("в процессе")
```
---
## 4. FFF — создание фейков
### Макросы объявления
```c
// void функция без аргументов
FAKE_VOID_FUNC(HAL_Init);
// void функция с аргументами
FAKE_VOID_FUNC(HAL_GPIO_WritePin,
GPIO_TypeDef*, // arg0
uint16_t, // arg1
GPIO_PinState); // arg2
// Функция с возвращаемым значением
FAKE_VALUE_FUNC(HAL_StatusTypeDef,
HAL_UART_Transmit,
UART_HandleTypeDef*, // arg0
uint8_t*, // arg1
uint16_t, // arg2
uint32_t); // arg3
// Без аргументов, с возвращаемым значением
FAKE_VALUE_FUNC(uint32_t, HAL_GetTick);
// Функция с переменным числом аргументов
FAKE_VOID_FUNC_VARARG(printf_fake, const char*, ...);
```
### Где объявлять
| Место | Назначение |
|---|---|
| `hal_fakes.h` | `DECLARE_FAKE_*` — объявления для хедера |
| `hal_fakes.c` | `DEFINE_FAKE_*` — определения, компилируется один раз |
| Тестовый файл | `DEFINE_FFF_GLOBALS` — ровно в одном `.c` файле |
```c
// hal_fakes.h
#pragma once
#include "fff.h"
#include "stm32f4xx_hal.h" // типы GPIO_TypeDef и т.д.
DECLARE_FAKE_VOID_FUNC(HAL_GPIO_WritePin, GPIO_TypeDef*, uint16_t, GPIO_PinState);
DECLARE_FAKE_VALUE_FUNC(uint32_t, HAL_GetTick);
DECLARE_FAKE_VALUE_FUNC(HAL_StatusTypeDef, HAL_UART_Transmit,
UART_HandleTypeDef*, uint8_t*, uint16_t, uint32_t);
```
```c
// hal_fakes.c
#include "hal_fakes.h"
DEFINE_FAKE_VOID_FUNC(HAL_GPIO_WritePin, GPIO_TypeDef*, uint16_t, GPIO_PinState);
DEFINE_FAKE_VALUE_FUNC(uint32_t, HAL_GetTick);
DEFINE_FAKE_VALUE_FUNC(HAL_StatusTypeDef, HAL_UART_Transmit,
UART_HandleTypeDef*, uint8_t*, uint16_t, uint32_t);
```
---
## 5. FFF — управление поведением
### Задать возвращаемое значение
```c
// Константное значение — возвращается при каждом вызове
HAL_UART_Transmit_fake.return_val = HAL_ERROR;
// Последовательность значений — каждый вызов берёт следующее
HAL_StatusTypeDef seq[] = {HAL_OK, HAL_OK, HAL_TIMEOUT};
SET_RETURN_SEQ(HAL_UART_Transmit, seq, 3);
// 1-й вызов → HAL_OK
// 2-й вызов → HAL_OK
// 3-й вызов → HAL_TIMEOUT
// 4-й и далее → последнее значение (HAL_TIMEOUT)
```
### Подставить кастомную реализацию
```c
// Эмулируем тикающий таймер
static uint32_t s_tick_ms = 0;
static uint32_t fake_get_tick_incrementing(void)
{
s_tick_ms += 10;
return s_tick_ms;
}
void test_timeout_after_100ms(void)
{
HAL_GetTick_fake.custom_fake = fake_get_tick_incrementing;
s_tick_ms = 0;
bool result = wait_with_timeout(100);
TEST_ASSERT_TRUE(result);
TEST_ASSERT_EQUAL(11, HAL_GetTick_fake.call_count);
}
```
> `custom_fake` имеет наивысший приоритет — если задан, `return_val` и
> `return_val_seq` игнорируются.
### Захват аргументов через custom_fake
```c
static uint8_t s_captured_data[64];
static uint16_t s_captured_size;
static HAL_StatusTypeDef fake_uart_capture(UART_HandleTypeDef *p_huart,
uint8_t *p_data,
uint16_t size,
uint32_t timeout)
{
s_captured_size = size;
memcpy(s_captured_data, p_data, size);
return HAL_OK;
}
void test_uart_sends_correct_payload(void)
{
HAL_UART_Transmit_fake.custom_fake = fake_uart_capture;
protocol_send_ping();
TEST_ASSERT_EQUAL(4, s_captured_size);
TEST_ASSERT_EQUAL_UINT8_ARRAY("\xAA\x01\x00\xBB", s_captured_data, 4);
}
```
---
## 6. FFF — проверка вызовов
### Счётчики и аргументы последнего вызова
```c
// Количество вызовов
TEST_ASSERT_EQUAL(2, HAL_GPIO_WritePin_fake.call_count);
// Аргументы последнего вызова
TEST_ASSERT_EQUAL_PTR(GPIOA, HAL_GPIO_WritePin_fake.arg0_val);
TEST_ASSERT_EQUAL(GPIO_PIN_5, HAL_GPIO_WritePin_fake.arg1_val);
TEST_ASSERT_EQUAL(GPIO_PIN_SET, HAL_GPIO_WritePin_fake.arg2_val);
```
### История всех вызовов
```c
// История аргументов — по умолчанию хранит FFF_ARG_HISTORY_LEN=50 вызовов
TEST_ASSERT_EQUAL(GPIO_PIN_SET, HAL_GPIO_WritePin_fake.arg2_history[0]);
TEST_ASSERT_EQUAL(GPIO_PIN_RESET, HAL_GPIO_WritePin_fake.arg2_history[1]);
```
### Порядок вызовов разных функций
```c
// FFF хранит глобальную историю вызовов всех фейков
void test_init_sequence_order(void)
{
module_init();
// HAL_Init должен вызваться раньше HAL_GPIO_Init
TEST_ASSERT_EQUAL_PTR(HAL_Init, fff.call_history[0]);
TEST_ASSERT_EQUAL_PTR(HAL_GPIO_Init, fff.call_history[1]);
}
```
### Функция не была вызвана
```c
void test_no_gpio_toggle_on_error(void)
{
HAL_UART_Transmit_fake.return_val = HAL_ERROR;
module_process();
TEST_ASSERT_EQUAL(0, HAL_GPIO_WritePin_fake.call_count);
}
```
---
## 7. Паттерны работы с HAL
### Паттерн: заглушка HAL для host-сборки
Создай `Tests/host/stubs/stm32f4xx_hal.h` — минимальный хедер с нужными типами:
```c
// Tests/host/stubs/stm32f4xx_hal.h
#pragma once
#include <stdint.h>
typedef struct { uint32_t placeholder; } GPIO_TypeDef;
typedef struct { uint32_t placeholder; } UART_HandleTypeDef;
typedef enum { GPIO_PIN_RESET = 0, GPIO_PIN_SET } GPIO_PinState;
typedef enum { HAL_OK = 0, HAL_ERROR, HAL_BUSY, HAL_TIMEOUT } HAL_StatusTypeDef;
#define GPIOA ((GPIO_TypeDef *)0x40020000)
#define GPIOB ((GPIO_TypeDef *)0x40020400)
#define GPIO_PIN_5 ((uint16_t)0x0020)
#define GPIO_PIN_13 ((uint16_t)0x2000)
```
### Паттерн: мок таймера
```c
static uint32_t s_mock_tick = 0;
void mock_tick_reset(void) { s_mock_tick = 0; }
void mock_tick_advance(uint32_t ms) { s_mock_tick += ms; }
static uint32_t fake_hal_get_tick(void) { return s_mock_tick; }
void setUp(void)
{
RESET_FAKE(HAL_GetTick);
HAL_GetTick_fake.custom_fake = fake_hal_get_tick;
mock_tick_reset();
}
void test_retry_stops_after_timeout(void)
{
HAL_UART_Transmit_fake.return_val = HAL_TIMEOUT;
bool result = uart_send_with_retry("data", 4, /*timeout_ms=*/300);
TEST_ASSERT_FALSE(result);
// За 300ms при retry каждые 100ms — ровно 3 попытки
TEST_ASSERT_EQUAL(3, HAL_UART_Transmit_fake.call_count);
}
```
### Паттерн: тест конечного автомата (FSM)
```c
void test_fsm_transitions_on_uart_error(void)
{
HAL_StatusTypeDef seq[] = {HAL_OK, HAL_ERROR};
SET_RETURN_SEQ(HAL_UART_Transmit, seq, 2);
fsm_init();
fsm_step();
fsm_step();
TEST_ASSERT_EQUAL(FSM_STATE_ERROR, fsm_get_state());
TEST_ASSERT_EQUAL(2, HAL_UART_Transmit_fake.call_count);
}
```
---
## 8. Организация фейков в проекте
```
Tests/
├── CMakeLists.txt
├── host/
│ ├── CMakeLists.txt
│ ├── stubs/
│ │ ├── stm32f4xx_hal.h # минимальные типы для компиляции на хосте
│ │ └── stm32f4xx_hal_conf.h
│ ├── fakes/
│ │ ├── hal_fakes.h # DECLARE_FAKE_* для всех HAL функций
│ │ └── hal_fakes.c # DEFINE_FAKE_* — компилируется один раз
│ ├── test_led.c
│ ├── test_uart.c
│ └── test_fsm.c
└── target/
└── ...
```
---
## 9. setUp / tearDown — правильный сброс
`RESET_FAKE` сбрасывает для одного фейка: счётчик вызовов, историю аргументов, `return_val`, `custom_fake`.
`FFF_RESET_HISTORY` сбрасывает глобальную историю порядка вызовов.
```c
void setUp(void)
{
RESET_FAKE(HAL_GPIO_WritePin);
RESET_FAKE(HAL_UART_Transmit);
RESET_FAKE(HAL_GetTick);
FFF_RESET_HISTORY();
// Восстанавливаем дефолтное поведение
HAL_GetTick_fake.return_val = 0;
HAL_UART_Transmit_fake.return_val = HAL_OK;
}
```
> Никогда не полагайся на порядок выполнения тестов. Каждый тест должен
> работать независимо — `setUp` обязан полностью сбрасывать состояние.
---
## 10. Ограничения и обходные пути
### `static` функции
FFF не может замокать `static` функции — они невидимы снаружи translation unit.
```c
// ❌ Нельзя замокать напрямую
static void internal_process(void) { ... }
// ✅ Решение — compile-time seam
#ifdef UNIT_TEST
void internal_process(void); // тест подставит свою реализацию
#else
static void internal_process(void) { ... }
#endif
```
### Настройка лимитов истории
```c
// Переопределяется перед включением fff.h
#define FFF_ARG_HISTORY_LEN 100 // сколько вызовов хранить на фейк
#define FFF_CALL_HISTORY_LEN 100 // размер глобальной истории вызовов
#include "fff.h"
```
### Глобальные хендлеры HAL
```c
// Тестовый файл — заглушка глобального хендлера
UART_HandleTypeDef huart1 = {0};
```
---
## 11. CMakeLists.txt для host-тестов
```cmake
# Tests/host/CMakeLists.txt
# Библиотека фейков — общая для всех тестов
add_library(hal_fakes STATIC fakes/hal_fakes.c)
target_include_directories(hal_fakes PUBLIC fakes/ stubs/)
target_link_libraries(hal_fakes PUBLIC ThirdParty)
# Макрос для регистрации тестов
function(add_host_test TEST_NAME TEST_SOURCES APP_SOURCES)
add_executable(${TEST_NAME} ${TEST_SOURCES} ${APP_SOURCES})
target_include_directories(${TEST_NAME} PRIVATE
${PROJECT_SOURCE_DIR}/App/Inc
)
target_compile_definitions(${TEST_NAME} PRIVATE UNIT_TEST)
target_link_libraries(${TEST_NAME} PRIVATE hal_fakes ThirdParty)
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
endfunction()
add_host_test(test_led
test_led.c
${PROJECT_SOURCE_DIR}/App/Src/led.c
)
add_host_test(test_uart
test_uart.c
${PROJECT_SOURCE_DIR}/App/Src/uart.c
)
```
> `UNIT_TEST` define передаётся в тестируемый код — используй его для
> compile-time seam при работе со `static` функциями.
---
## 12. Запуск тестов
```bash
# 1. Конфигурация
cmake --preset host-debug
# 2. Сборка
cmake --build --preset host-debug-build
# 3. Запуск всех тестов
ctest --preset host-debug-test
# Запуск конкретного теста с полным выводом
ctest --preset host-debug-test -R test_led -V
# Запуск напрямую — полный вывод Unity
./build-host-debug/Tests/host/test_led
# Одной командой
cmake --preset host-debug && \
cmake --build --preset host-debug-build && \
ctest --preset host-debug-test
```
### Пример вывода при успехе
```
test_led.c:28:test_led_on_sets_gpio_high:PASS
test_led.c:35:test_led_off_sets_gpio_low:PASS
-----------------------
2 Tests 0 Failures 0 Ignored
OK
```
### Пример вывода при провале
```
test_led.c:29:test_led_on_sets_gpio_high:FAIL:
Expected 1 Was 0
-----------------------
2 Tests 1 Failures 0 Ignored
FAIL
```

View file

@ -1,312 +0,0 @@
# Юнит тестирование CTest, Unity, CMakePresets
- [Юнит тестирование CTest, Unity, CMakePresets](#юнит-тестирование-ctest-unity-cmakepresets)
- [1. Общая архитектура](#1-общая-архитектура)
- [1.1 Структура проекта](#11-структура-проекта)
- [1.2 CMake конфигурация](#12-cmake-конфигурация)
- [1.3 CMake Presets](#13-cmake-presets)
- [2. Использование](#2-использование)
- [2.1 CLI](#21-cli)
- [3. Ключевые принципы](#3-ключевые-принципы)
- [4. Итоговый Workflow](#4-итоговый-workflow)
## 1. Общая архитектура
| Компонент | Назначение | Где используется |
| ------------------------ | --------------------------------------------- | ---------------- |
| **Unity** | Фреймворк unit-тестирования (ThrowTheSwitch) | Host + Target |
| **SEGGER RTT** | Вывод логов через отладчик | Только Target |
| **ThirdParty INTERFACE** | Централизованная точка для всех зависимостей | Везде |
| **CMake Presets** | Конфигурационные профили для разных окружений | CLI + VSCode |
| **CTest** | Запуск и отчёты по тестам | Host тесты |
### 1.1 Структура проекта
```bash
ProjectRoot/
├── CMakeLists.txt # Корневой CMake
├── CMakePresets.json # Presets для host/target
├── cmake/
│ └── toolchain-stm32f4.cmake # ARM GCC toolchain
├── App/ # Код приложения
│ ├── Src/
│ │ └── app.c # Тестируемый код
│ └── Inc/
│ └── app.h
├── Tests/ # Все тесты
│ ├── CMakeLists.txt # enable_testing() + опции
│ ├── host/ # Host тесты (Mac)
│ │ ├── CMakeLists.txt
│ │ └── test_host_simple.c
│ └── target/ # Target тесты (STM32)
│ ├── CMakeLists.txt
│ ├── test_main.c # Unity runner для МК
│ └── test_gpio.c
├── ThirdParty/ # Внешние зависимости
│ ├── CMakeLists.txt # INTERFACE библиотека
│ ├── Unity/ # git submodules
│ │ └── src/
│ │ ├── unity.c
│ │ └── unity.h
│ └── SEGGER_RTT/
│ ├── RTT/
│ └── wrapper/
│ └── CMakeLists.txt # PUBLIC include пути
├── build-host/ # Сборка для Mac (генерируется)
└── build-target/ # Сборка для STM32 (генерируется)
```
### 1.2 CMake конфигурация
1. Корневой **CMakeLists.txt**
```cmake
cmake_minimum_required(VERSION 3.22)
project(MySTM32Project C ASM)
# Добавляем все компоненты
add_subdirectory(ThirdParty) # Зависимости
add_subdirectory(App) # Код приложения
add_subdirectory(Tests) # Тесты
```
1. ThirdParty/CMakeLists.txt (**INTERFACE** библиотека)
• INTERFACE библиотека НЕ генерирует .a/.so файлы
• Служит контейнером для передачи зависимостей
• Наследует PUBLIC свойства от связанных библиотек
• Передаёт всё потребителям одной строкой target_link_libraries(MyApp ThirdParty)
Как работает **INTERFACE**:
```txt
1. ThirdParty INTERFACE создана (без файлов)
2. target_link_libraries(ThirdParty INTERFACE SeggerRTT)
→ ThirdParty наследует PUBLIC свойства SeggerRTT:
- include пути: RTT/
- defines: RTT_MODE=1
- библиотеку: libSeggerRTT.a
3. target_link_libraries(MyApp ThirdParty)
→ MyApp автоматически получает ВСЁ от ThirdParty
```
1. **Tests/CMakeLists.txt** (входная точка для тестов)
```cmake
# ⚠️ ОБЯЗАТЕЛЬНО! Регистрирует тесты в CTest
enable_testing()
# Опции для выбора типа тестов
option(BUILD_TESTS_HOST "Build host (Mac) unit tests" ON)
option(BUILD_TESTS_TARGET "Build STM32 target tests" OFF)
# Host тесты (Mac)
if(BUILD_TESTS_HOST)
message(STATUS "🔧 Building host tests")
add_subdirectory(host)
endif()
# Target тесты (STM32)
if(BUILD_TESTS_TARGET)
message(STATUS "🔧 Building target tests")
add_subdirectory(target)
endif()
```
1. **Tests/host/CMakeLists.txt** (host тесты)
```cmake
# Тестовый исполняемый файл
add_executable(test_host_simple
test_host_simple.c # Код тестов
${PROJECT_SOURCE_DIR}/App/Src/app.c # Тестируемый код
)
# Include пути
target_include_directories(test_host_simple PRIVATE
${PROJECT_SOURCE_DIR}/App/Inc # Заголовки приложения
${PROJECT_SOURCE_DIR}/ThirdParty/Unity/src # unity.h
)
# Линковка с ThirdParty → автоматически получаем Unity
target_link_libraries(test_host_simple PRIVATE ThirdParty)
# ⚠️ КРИТИЧНО! Регистрация в CTest
add_test(NAME test_host_simple
COMMAND test_host_simple
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) # build-host/
```
1. **Tests/target/CMakeLists.txt** (target тесты)
```cmake
# Тестовый ELF для STM32
add_executable(test_gpio.elf
test_main.c # Unity runner
test_gpio.c # Тесты GPIO
${PROJECT_SOURCE_DIR}/Src/hw/gpio_driver.c
)
target_include_directories(test_gpio.elf PRIVATE
${PROJECT_SOURCE_DIR}/Src/hw
)
# ThirdParty даёт Unity + RTT
target_link_libraries(test_gpio.elf PRIVATE ThirdParty)
# Линкер скрипт для STM32
target_link_options(test_gpio.elf PRIVATE
-T${PROJECT_SOURCE_DIR}/cmake/STM32F407VETx_flash.ld
-Wl,--gc-sections
-Wl,--print-memory-usage
)
# Отчёт размера
add_custom_command(TARGET test_gpio.elf POST_BUILD
COMMAND ${CMAKE_SIZE} $<TARGET_FILE:test_gpio.elf>
)
```
### 1.3 CMake Presets
```json
{
"version": 6,
"configurePresets": [
{
"name": "host-debug",
"displayName": "🖥️ Host Debug Tests",
"description": "Unit tests for Mac (AppleClang)",
"binaryDir": "${sourceDir}/build-host",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"BUILD_TESTS_HOST": "ON",
"BUILD_TESTS_TARGET": "OFF",
"UNITY_ENABLED": "ON",
"SEGGER_RTT_ENABLED": "OFF"
}
},
{
"name": "target-debug",
"displayName": "🎯 STM32F407 Target Tests",
"description": "Integration tests for STM32",
"binaryDir": "${sourceDir}/build-target",
"generator": "Ninja",
"toolchainFile": "${sourceDir}/cmake/toolchain-stm32f4.cmake",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"BUILD_TESTS_HOST": "OFF",
"BUILD_TESTS_TARGET": "ON",
"UNITY_ENABLED": "ON",
"SEGGER_RTT_ENABLED": "ON"
}
}
],
"buildPresets": [
{
"name": "host-debug-build",
"configurePreset": "host-debug",
"displayName": "Build Host Tests",
"targets": ["test_host_simple"]
},
{
"name": "target-debug-flash",
"configurePreset": "target-debug",
"displayName": "Flash Target Tests",
"targets": ["test_gpio.elf"]
}
],
"testPresets": [
{
"name": "host-debug-test",
"configurePreset": "host-debug",
"displayName": "🧪 Run Host Tests",
"output": {
"outputOnFailure": true,
"verbosity": "detailed"
}
}
]
}
```
Что делают **presets**:
| Preset | Компилятор | Опции | Результат |
| ------------ | ---------- | --------------------- | --------------------------- |
| host-debug | AppleClang | BUILD_TESTS_HOST=ON | build-host/test_host_simple |
| target-debug | ARM GCC | BUILD_TESTS_TARGET=ON | build-target/test_gpio.elf |
## 2. Использование
### 2.1 CLI
**Host** тесты
```bash
# 1. Конфигурация
cmake --preset host-debug
# 2. Сборка
cmake --build --preset host-debug-build
# 3. Запуск тестов
ctest --preset host-debug-test
# Или всё одной командой:
cmake --preset host-debug && \
cmake --build --preset host-debug-build && \
ctest --preset host-debug-test
```
**Target** тесты
```bash
# 1. Конфигурация
cmake --preset target-debug
# 2. Сборка
cmake --build --preset target-debug-flash
# 3. Прошивка и запуск
probe-rs run --chip STM32F407VETx build-target/test_gpio.elf
# Смотрим вывод RTT в терминале!
```
## 3. Ключевые принципы
1. **INTERFACE** библиотека **ThirdParty**
• Контейнер зависимостей без генерации файлов
• Наследует **PUBLIC** свойства от связанных библиотек
• Передаёт всё одной строкой `target_link_libraries(App ThirdParty)`
2. **CMake Presets**
• Один файл для всех конфигураций (host/target)
• Опции управляют включением тестов ( BUILD_TESTS_HOST/TARGET )
• Build presets с targets предотвращают сборку лишнего
3. **CTest** интеграция
`enable_testing()` в `Tests/CMakeLists.txt`
`add_test()` для каждого теста
`WORKING_DIRECTORY ${CMAKE_BINARY_DIR}` критично!
4. Фиксы **Unity**
• Явное `target_include_directories` для `Unity/src`
`Unity CMakeLists.txt` не экспортирует **PUBLIC** пути
## 4. Итоговый Workflow
| Шаг | Действие |
| :---- | ------------------------------------------------------------------------------------- |
| **1** | Написали код в `App/Src/app.c` |
| **2** | Написали `host` тест в `Tests/host/test_host_simple.c` |
| **3** | `cmake --preset host-debug` → AppleClang, build-host/, Unity enabled |
| **4** | `cmake --build --preset host-debug-build` → Только test_host_simple собирается |
| **5** | `ctest --preset host-debug-test` → Тесты выполняются, отчёт CTest |
| **6** | Всё работает! Добавили target тест в `Tests/target/` |
| **7** | `cmake --preset target-debug` → ARM GCC, build-target/, Unity + RTT enabled |
| **8** | `cmake --build --preset target-debug-flash``test_gpio.elf` создан |
| **9** | `probe-rs run --chip STM32F407VETx test_gpio.elf` → Прошивка, запуск, вывод через RTT |

View file

@ -1,16 +1,51 @@
function(add_host_test TEST_NAME TEST_SOURCES) # tests/host/CMakeLists.txt
add_executable(${TEST_NAME} ${TEST_SOURCES})
target_link_libraries(${TEST_NAME} PRIVATE lib_external) # -----------------------------------------------------------------------------
# Вспомогательная функция добавления host-теста.
#
# Использование: add_host_test( NAME <имя теста> SOURCES <тест.c>
# <тестируемый_модуль.c> ... MOCKS <путь к папке с stub-хедерами> #
# опционально )
#
# Каждый тест — отдельный исполняемый файл. CTest запускает их все.
# -----------------------------------------------------------------------------
function(add_host_test)
cmake_parse_arguments(ARG "" "NAME" "SOURCES;MOCKS" ${ARGN})
add_executable(${ARG_NAME} ${ARG_SOURCES})
target_link_libraries(${ARG_NAME} PRIVATE lib_external)
# stub-хедеры подключаются как PRIVATE — не торчат в других таргетах
foreach(mock_dir IN LISTS ARG_MOCKS)
target_include_directories(${ARG_NAME} PRIVATE ${mock_dir})
endforeach()
# публичные хедеры BSP нужны для #include "bsp/led.h"
target_include_directories(${ARG_NAME}
PRIVATE ${PROJECT_SOURCE_DIR}/bsp/led/include)
add_test( add_test(
NAME ${TEST_NAME} NAME ${ARG_NAME}
COMMAND ${TEST_NAME} COMMAND ${ARG_NAME}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
endfunction() endfunction()
# Подключаешь тесты лаконично add_host_test( test_host_simple test_host_simple.c # =============================================================================
# ${PROJECT_SOURCE_DIR}/lib/hal/src/app.c # тестируемый модуль ) # Host тесты
# =============================================================================
# add_host_test(test_host_second test_host_second.c set(BSP_MOCKS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mocks)
# ${PROJECT_SOURCE_DIR}/lib/hal/src/app.c)
# -----------------------------------------------------------------------------
# bsp_led
# -----------------------------------------------------------------------------
add_host_test(
NAME
test_bsp_led
SOURCES
test_bsp_led.c
${PROJECT_SOURCE_DIR}/bsp/led/src/led.c # тестируемый модуль напрямую
MOCKS
${BSP_MOCKS_DIR} # fsl_gpio.h, pin_mux.h, board.h
)

584
tests/host/README.md Normal file
View file

@ -0,0 +1,584 @@
# Host unit-тесты: Unity + FFF
## Содержание
- [1. Две категории тестируемых модулей](#1-две-категории-тестируемых-модулей)
- [2. Структура директорий](#2-структура-директорий)
- [3. Структура тестового файла](#3-структура-тестового-файла)
- [4. Unity — assertion API](#4-unity--assertion-api)
- [5. FFF — создание фейков](#5-fff--создание-фейков)
- [6. FFF — управление поведением](#6-fff--управление-поведением)
- [7. FFF — проверка вызовов](#7-fff--проверка-вызовов)
- [8. Работа со stub-хедерами NXP SDK](#8-работа-со-stub-хедерами-nxp-sdk)
- [9. Ловушки и обходные пути](#9-ловушки-и-обходные-пути)
- [10. setUp / tearDown — правильный сброс](#10-setup--teardown--правильный-сброс)
- [11. CMakeLists.txt для host-тестов](#11-cmakeliststxt-для-host-тестов)
- [12. Запуск тестов](#12-запуск-тестов)
---
## 1. Две категории тестируемых модулей
Прежде чем писать тест — определи к какой категории относится модуль. От этого зависит какой инструментарий нужен.
### Категория A — платформонезависимые модули
Модули без единого вызова NXP SDK: парсеры, протоколы, конечные автоматы, алгоритмы, структуры данных. Зависят только от стандартной библиотеки C.
**Инструментарий: только Unity.**
```bash
тест (Unity assertions)
тестируемый модуль (protocol.c, fsm.c, ...)
stdlib (string.h, stdint.h, ...) ← всё доступно на хосте нативно
```
Пример: тест JSON-протокола `firmware_test`, тест логики Test runner, тест FSM.
```c
/* tests/host/test_protocol.c */
#include "unity.h"
#include "protocol.h" /* платформонезависимый модуль */
void test_parse_run_all_command(void) {
proto_cmd_t cmd;
int r = proto_parse("{\"type\":\"cmd\",\"cmd\":\"run_all\"}\n", &cmd);
TEST_ASSERT_EQUAL(0, r);
TEST_ASSERT_EQUAL(CMD_RUN_ALL, cmd.type);
}
```
### Категория B — BSP-модули (привязка к NXP SDK)
Модули из `bsp/` которые вызывают `fsl_gpio.h`, `fsl_lpuart.h`, NXP USB stack и т.д. На хосте этих хедеров нет — нужны stub-хедеры и fff-фейки.
**Инструментарий: Unity + fff + stub-хедеры.**
```bash
тест (Unity assertions)
тестируемый модуль (bsp/led/src/led.c)
fff-фейки (FAKE_VOID_FUNC, FAKE_VALUE_FUNC) ← подменяют NXP SDK функции
stub-хедеры (tests/host/mocks/fsl_gpio.h) ← подменяют NXP SDK хедеры
```
Принцип **seam (шов)**: тестируемый код не знает что вызывает фейк — линковщик и include path подставляют нужную реализацию в зависимости от сборки.
---
## 2. Структура директорий
```bash
tests/host/
├── CMakeLists.txt
├── mocks/ # stub-хедеры, заменяющие NXP SDK на хосте
│ ├── fsl_gpio.h # минимальные типы + сигнатуры GPIO
│ ├── fsl_lpuart.h
│ ├── pin_mux.h # зеркало макросов пинов из generated/
│ └── board.h
├── test_led.c # категория B — BSP-модуль
├── test_protocol.c # категория A — платформонезависимый
└── test_runner_logic.c # категория A
```
`mocks/` подключается как include path с более высоким приоритетом чем `sdk/`. Компилятор найдёт `fsl_gpio.h` из `mocks/` раньше чем из SDK — `led.c` компилируется на хосте без единого изменения в BSP-коде.
---
## 3. Структура тестового файла
### Категория A — без фейков
```c
#include "unity.h"
#include "protocol.h" /* тестируемый модуль */
void setUp(void) { /* сброс состояния если нужен */ }
void tearDown(void) { }
void test_something(void) {
TEST_ASSERT_EQUAL(expected, actual);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_something);
return UNITY_END();
}
```
### Категория B — с fff-фейками
```c
#include "unity.h"
#include "fff.h"
DEFINE_FFF_GLOBALS; /* ровно один раз на весь .c файл */
/* 1. Подключаем stub-хедер с типами */
#include "fsl_gpio.h"
/* 2. Объявляем фейки для функций которые вызывает тестируемый модуль */
FAKE_VOID_FUNC(GPIO_PinInit, GPIO_Type *, uint32_t, const gpio_pin_config_t *);
FAKE_VOID_FUNC(GPIO_PinWrite, GPIO_Type *, uint32_t, uint8_t);
/* 3. Подключаем тестируемый модуль — ПОСЛЕ фейков */
#include "bsp/led.h"
void setUp(void) {
RESET_FAKE(GPIO_PinInit);
RESET_FAKE(GPIO_PinWrite);
FFF_RESET_HISTORY();
led_init();
}
void tearDown(void) { }
void test_led_on_writes_gpio_low(void) {
led_on(LED_HEARTBEAT);
TEST_ASSERT_EQUAL_UINT8(0U, GPIO_PinWrite_fake.arg2_val); /* active LOW */
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_led_on_writes_gpio_low);
return UNITY_END();
}
```
---
## 4. Unity — assertion API
### Целые числа
```c
TEST_ASSERT_EQUAL(expected, actual)
TEST_ASSERT_EQUAL_INT(expected, actual)
TEST_ASSERT_EQUAL_INT8(expected, actual)
TEST_ASSERT_EQUAL_INT16(expected, actual)
TEST_ASSERT_EQUAL_INT32(expected, actual)
TEST_ASSERT_EQUAL_UINT8(expected, actual)
TEST_ASSERT_EQUAL_UINT32(expected, actual)
TEST_ASSERT_NOT_EQUAL(expected, actual)
```
### Числа с плавающей точкой
```c
TEST_ASSERT_EQUAL_FLOAT(expected, actual)
TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual) /* |actual - expected| < delta */
TEST_ASSERT_EQUAL_DOUBLE(expected, actual)
```
### Булевые значения
```c
TEST_ASSERT_TRUE(condition)
TEST_ASSERT_FALSE(condition)
TEST_ASSERT_NULL(pointer)
TEST_ASSERT_NOT_NULL(pointer)
```
### Указатели
```c
TEST_ASSERT_EQUAL_PTR(expected, actual)
```
### Строки и массивы
```c
TEST_ASSERT_EQUAL_STRING(expected, actual)
TEST_ASSERT_EQUAL_MEMORY(expected, actual, len)
TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, len)
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, len)
```
### Диапазоны
```c
TEST_ASSERT_INT_WITHIN(delta, expected, actual)
TEST_ASSERT_UINT32_WITHIN(delta, expected, actual)
```
### Явный провал / пропуск
```c
TEST_FAIL()
TEST_FAIL_MESSAGE("причина")
TEST_IGNORE()
TEST_IGNORE_MESSAGE("в процессе")
```
---
## 5. FFF — создание фейков
### Макросы объявления
```c
/* void-функция без аргументов */
FAKE_VOID_FUNC(HAL_Init);
/* void-функция с аргументами */
FAKE_VOID_FUNC(GPIO_PinInit, GPIO_Type *, uint32_t, const gpio_pin_config_t *);
/* функция с возвращаемым значением */
FAKE_VALUE_FUNC(status_t, LPUART_WriteBlocking, LPUART_Type *, const uint8_t *, size_t);
/* без аргументов с возвращаемым значением */
FAKE_VALUE_FUNC(uint32_t, get_tick_ms);
/* переменное число аргументов */
FAKE_VOID_FUNC_VARARG(debug_printf, const char *, ...);
```
### Расположение фейков в файле
Фейки объявляются **в тестовом .c файле** прямо перед `#include` тестируемого модуля. Для небольших проектов отдельный `fakes.h/fakes.c` избыточен — каждый тестовый файл объявляет только те фейки, которые нужны именно ему.
```c
/* Правильный порядок в тестовом файле */
#include "unity.h"
#include "fff.h"
DEFINE_FFF_GLOBALS; /* 1. глобальный контекст fff */
#include "fsl_gpio.h" /* 2. stub-хедер с типами */
FAKE_VOID_FUNC(GPIO_PinWrite, GPIO_Type *, uint32_t, uint8_t); /* 3. фейк */
#include "bsp/led.h" /* 4. тестируемый модуль — последним */
```
---
## 6. FFF — управление поведением
### Задать возвращаемое значение
```c
/* константа — при каждом вызове */
LPUART_WriteBlocking_fake.return_val = kStatus_Fail;
/* последовательность — каждый вызов берёт следующее */
status_t seq[] = {kStatus_Success, kStatus_Success, kStatus_Timeout};
SET_RETURN_SEQ(LPUART_WriteBlocking, seq, 3);
/* 1-й вызов → kStatus_Success */
/* 2-й вызов → kStatus_Success */
/* 3-й вызов → kStatus_Timeout */
/* 4-й и далее → последнее (kStatus_Timeout) */
```
### custom_fake — кастомная реализация
```c
/* эмуляция тикающего таймера */
static uint32_t s_tick = 0;
static uint32_t fake_tick_inc(void) { s_tick += 10; return s_tick; }
void test_timeout_fires_after_100ms(void) {
get_tick_ms_fake.custom_fake = fake_tick_inc;
s_tick = 0;
bool result = wait_with_timeout(100);
TEST_ASSERT_TRUE(result);
TEST_ASSERT_EQUAL(11, get_tick_ms_fake.call_count);
}
```
> `custom_fake` имеет наивысший приоритет — если задан, `return_val` и
> `return_val_seq` игнорируются.
### custom_fake — захват аргументов по значению
Используй когда нужно проверить содержимое структуры, переданной по указателю.
**Не используй `arg_history[]` для указателей на стековые переменные** — после возврата из тестируемой функции это dangling pointer (ASAN: `stack-use-after-return`).
```c
/* НЕПРАВИЛЬНО — cfg живёт на стеке led_init(), после return — dangling */
led_init();
const gpio_pin_config_t *cfg = GPIO_PinInit_fake.arg2_history[0]; /* UB! */
TEST_ASSERT_EQUAL(kGPIO_DigitalOutput, cfg->direction);
/* ПРАВИЛЬНО — копируем по значению пока стек ещё жив */
static gpio_pin_config_t s_captured[2];
static int s_idx = 0;
static void capture_cfg(GPIO_Type *base, uint32_t pin,
const gpio_pin_config_t *cfg) {
(void)base; (void)pin;
if (s_idx < 2) s_captured[s_idx++] = *cfg; /* копия по значению */
}
void setUp(void) {
RESET_FAKE(GPIO_PinInit);
s_idx = 0;
GPIO_PinInit_fake.custom_fake = capture_cfg; /* подключить ДО вызова */
led_init();
}
void test_init_configures_as_output(void) {
TEST_ASSERT_EQUAL(kGPIO_DigitalOutput, s_captured[0].direction);
}
```
---
## 7. FFF — проверка вызовов
### Счётчик и аргументы последнего вызова
```c
TEST_ASSERT_EQUAL(2, GPIO_PinWrite_fake.call_count);
TEST_ASSERT_EQUAL_UINT8(0U, GPIO_PinWrite_fake.arg2_val); /* последний вызов */
```
### История всех вызовов
```c
/* arg_history хранит FFF_ARG_HISTORY_LEN (по умолчанию 50) последних вызовов */
/* Используй только для скалярных типов и не-стековых указателей */
TEST_ASSERT_EQUAL_UINT8(0U, GPIO_PinWrite_fake.arg2_history[0]);
TEST_ASSERT_EQUAL_UINT8(1U, GPIO_PinWrite_fake.arg2_history[1]);
```
### Порядок вызовов разных функций
```c
void test_init_sequence_order(void) {
board_init();
/* clock_init должен вызваться раньше gpio_init */
TEST_ASSERT_EQUAL_PTR(clock_init, fff.call_history[0]);
TEST_ASSERT_EQUAL_PTR(gpio_init, fff.call_history[1]);
}
```
### Функция не была вызвана
```c
void test_no_gpio_write_on_error(void) {
LPUART_WriteBlocking_fake.return_val = kStatus_Fail;
module_process();
TEST_ASSERT_EQUAL(0, GPIO_PinWrite_fake.call_count);
}
```
---
## 8. Работа со stub-хедерами NXP SDK
### Зачем нужны stub-хедеры
NXP SDK хедеры (`fsl_gpio.h`, `fsl_lpuart.h` и т.д.) не компилируются на хосте — они тянут платформенные регистровые определения для Cortex-M7. Stub-хедер в `tests/host/mocks/` содержит только минимально необходимые типы и сигнатуры функций.
### Как stub-хедер «перекрывает» SDK
В CMakeLists для тестового таргета `mocks/` добавляется в include path **до** SDK:
```cmake
target_include_directories(test_led PRIVATE
${CMAKE_SOURCE_DIR}/tests/host/mocks # ← ищется первым
${CMAKE_SOURCE_DIR}/bsp/led/include
)
```
Компилятор найдёт `fsl_gpio.h` из `mocks/` раньше чем из `sdk/``led.c` компилируется без изменений.
### Что должно быть в stub-хедере
Только то, что реально используется в тестируемом `.c` файле. Не копировать весь SDK хедер.
```c
/* tests/host/mocks/fsl_gpio.h */
#pragma once
#include <stdint.h>
typedef struct { uint32_t reserved[64]; } GPIO_Type;
typedef enum { kGPIO_DigitalInput = 0U, kGPIO_DigitalOutput = 1U } gpio_pin_direction_t;
typedef enum { kGPIO_NoIntmode = 0U } gpio_interrupt_mode_t;
typedef struct {
gpio_pin_direction_t direction;
uint8_t outputLogic;
gpio_interrupt_mode_t interruptMode;
} gpio_pin_config_t;
/* сигнатуры — реализации предоставляет fff */
void GPIO_PinInit(GPIO_Type *base, uint32_t pin, const gpio_pin_config_t *config);
void GPIO_PinWrite(GPIO_Type *base, uint32_t pin, uint8_t output);
```
### stub pin_mux.h — зеркало макросов пинов
`pin_mux.h` из `generated/` тоже недоступен на хосте. Создаём stub который зеркалит реальные значения:
```c
/* tests/host/mocks/pin_mux.h */
#pragma once
#include "fsl_gpio.h"
static GPIO_Type stub_GPIO3;
#define BOARD_INITPINS_UserLed1_GPIO (&stub_GPIO3)
#define BOARD_INITPINS_UserLed1_GPIO_PIN 3U
#define BOARD_INITPINS_UserLed2_GPIO (&stub_GPIO3)
#define BOARD_INITPINS_UserLed2_GPIO_PIN 4U
```
> При изменении пинов в `generated/pin_mux.h` — обновить соответствующий stub вручную.
---
## 9. Ловушки и обходные пути
### `static` функции
FFF не может замокать `static` функции — они невидимы снаружи translation unit.
```c
/* ❌ нельзя замокать напрямую */
static void internal_process(void) { ... }
/* ✅ compile-time seam */
#ifdef UNIT_TEST
void internal_process(void); /* тест подставит свою реализацию */
#else
static void internal_process(void) { ... }
#endif
```
### Dangling pointer из arg_history
`arg_history[]` хранит указатели — **не** копии. Для структур передаваемых по указателю из функций с коротким временем жизни (локальные переменные) использовать `custom_fake` с копированием по значению. Подробнее — в разделе 6.
### Настройка лимитов истории
```c
/* переопределить перед включением fff.h */
#define FFF_ARG_HISTORY_LEN 100
#define FFF_CALL_HISTORY_LEN 100
#include "fff.h"
```
### Стандартные хедеры в BSP
BSP-модули должны явно включать `<stddef.h>`, `<stdint.h>`, `<stdbool.h>` — не полагаться на транзитивное подтягивание через NXP SDK. На хосте этот транзит отсутствует и компиляция упадёт с `undeclared identifier 'size_t'`.
---
## 10. setUp / tearDown — правильный сброс
`RESET_FAKE` сбрасывает для одного фейка: счётчик вызовов, историю аргументов, `return_val`, `custom_fake`.
`FFF_RESET_HISTORY` сбрасывает глобальную историю порядка вызовов.
```c
void setUp(void)
{
RESET_FAKE(GPIO_PinInit);
RESET_FAKE(GPIO_PinWrite);
FFF_RESET_HISTORY();
/* восстановить дефолтное поведение если нужно */
LPUART_WriteBlocking_fake.return_val = kStatus_Success;
}
```
> Никогда не полагайся на порядок выполнения тестов. Каждый тест должен
> работать независимо — `setUp` обязан полностью сбрасывать состояние.
---
## 11. CMakeLists.txt для host-тестов
```cmake
# tests/host/CMakeLists.txt
function(add_host_test)
cmake_parse_arguments(ARG "" "NAME" "SOURCES;MOCKS" ${ARGN})
add_executable(${ARG_NAME} ${ARG_SOURCES})
target_link_libraries(${ARG_NAME} PRIVATE lib_external)
# mocks/ подключается первым — перекрывает SDK хедеры
foreach(mock_dir IN LISTS ARG_MOCKS)
target_include_directories(${ARG_NAME} PRIVATE ${mock_dir})
endforeach()
add_test(
NAME ${ARG_NAME}
COMMAND ${ARG_NAME}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
endfunction()
set(MOCKS ${CMAKE_CURRENT_SOURCE_DIR}/mocks)
# категория A — платформонезависимый, без mocks
add_host_test(
NAME test_protocol
SOURCES test_protocol.c
${PROJECT_SOURCE_DIR}/firmware/test/src/protocol.c
)
# категория B — BSP-модуль, нужны mocks
add_host_test(
NAME test_led
SOURCES test_led.c
${PROJECT_SOURCE_DIR}/bsp/led/src/led.c
MOCKS ${MOCKS}
)
```
---
## 12. Запуск тестов
```bash
# конфигурация (один раз или после изменения CMakeLists)
cmake --preset host-debug
# сборка + тесты одной командой
just build::test-host
# или по шагам:
cmake --build --preset host-debug-build
ctest --preset host-debug-test
# конкретный тест с полным выводом Unity
ctest --preset host-debug-test -R test_led -V
# напрямую — видно весь вывод без CTest-обёртки
./build/host-debug/tests/host/test_led
```
### Пример вывода при успехе
```bash
test_led.c:58:test_led_init_calls_gpio_init_for_each_led:PASS
test_led.c:64:test_led_init_configures_as_output:PASS
test_led.c:71:test_led_init_output_logic_is_high:PASS
test_led.c:77:test_led_on_writes_gpio_low:PASS
-----------------------
14 Tests 0 Failures 0 Ignored
OK
```
### Пример вывода при провале
```bash
test_led.c:78:test_led_on_writes_gpio_low:FAIL:
Expected 0 Was 1
-----------------------
14 Tests 1 Failures 0 Ignored
FAIL
```

2
tests/host/mocks/board.h Normal file
View file

@ -0,0 +1,2 @@
#pragma once
/* Stub board.h для host-тестов — led.c включает его транзитивно через bsp_board */

View file

@ -0,0 +1,40 @@
#pragma once
/**
* Stub fsl_gpio.h для host-тестов.
* Содержит только типы и сигнатуры используемые в bsp/led/src/led.c.
* fff предоставляет реализации через FAKE_VOID_FUNC.
*/
#include <stdint.h>
/* GPIO peripheral base pointer type */
typedef struct
{
uint32_t reserved[64];
} GPIO_Type;
/* Pin direction */
typedef enum
{
kGPIO_DigitalInput = 0U,
kGPIO_DigitalOutput = 1U,
} gpio_pin_direction_t;
/* Interrupt mode — не используется в LED, но нужен для компиляции */
typedef enum
{
kGPIO_NoIntmode = 0U,
} gpio_interrupt_mode_t;
/* Pin config struct */
typedef struct
{
gpio_pin_direction_t direction;
uint8_t outputLogic;
gpio_interrupt_mode_t interruptMode;
} gpio_pin_config_t;
/* Functions — реализуются через fff в тест-файле */
void GPIO_PinInit(GPIO_Type *base, uint32_t pin, const gpio_pin_config_t *config);
void GPIO_PinWrite(GPIO_Type *base, uint32_t pin, uint8_t output);

View file

@ -0,0 +1,22 @@
#pragma once
/**
* Stub pin_mux.h для host-тестов.
* Макросы пинов зеркалят реальные значения из generated/pin_mux.h.
* GPIO-инстансы статические объекты-заглушки, адреса уникальны.
*/
#include "fsl_gpio.h"
/* Заглушки GPIO-периферии */
static GPIO_Type stub_GPIO3;
/* UserLed1 → GPIO3 pin 3 (LED_HEARTBEAT) */
#define BOARD_INITPINS_UserLed1_GPIO (&stub_GPIO3)
#define BOARD_INITPINS_UserLed1_GPIO_PIN 3U
#define BOARD_INITPINS_UserLed1_INIT_GPIO_VALUE 1U
/* UserLed2 → GPIO3 pin 4 (LED_APP) */
#define BOARD_INITPINS_UserLed2_GPIO (&stub_GPIO3)
#define BOARD_INITPINS_UserLed2_GPIO_PIN 4U
#define BOARD_INITPINS_UserLed2_INIT_GPIO_VALUE 1U

191
tests/host/test_bsp_led.c Normal file
View file

@ -0,0 +1,191 @@
#include "fff.h"
#include "unity.h"
DEFINE_FFF_GLOBALS;
/* ── Моки GPIO-функций ───────────────────────────────────────────────
* Должны быть объявлены ДО включения тестируемого модуля,
* чтобы fff-заглушки заменили реальные функции при линковке.
* */
#include "fsl_gpio.h"
FAKE_VOID_FUNC(GPIO_PinInit, GPIO_Type *, uint32_t, const gpio_pin_config_t *);
FAKE_VOID_FUNC(GPIO_PinWrite, GPIO_Type *, uint32_t, uint8_t);
/* ── Capture для gpio_pin_config_t ──────────────────────────────────
* fff хранит arg2_history как указатель но cfg живёт на стеке
* led_init() и становится dangling после возврата.
* (ASAN правильно детектирует это как stack-use-after-return)
*
* Решение: custom_fake копирует структуру по значению в момент вызова,
* пока стек led_init() ещё жив.
* */
static gpio_pin_config_t s_captured_cfg[2];
static int s_capture_idx = 0;
static void GPIO_PinInit_capture(GPIO_Type *base, uint32_t pin, const gpio_pin_config_t *cfg)
{
(void) base;
(void) pin;
if (s_capture_idx < 2)
{
s_captured_cfg[s_capture_idx++] = *cfg; /* копируем по значению */
}
}
/* ── Тестируемый модуль ──────────────────────────────────────────────── */
#include "bsp/led.h"
/* ── setUp / tearDown ────────────────────────────────────────────────── */
void setUp(void)
{
RESET_FAKE(GPIO_PinInit);
RESET_FAKE(GPIO_PinWrite);
FFF_RESET_HISTORY();
/* подключаем capture до вызова led_init */
s_capture_idx = 0;
GPIO_PinInit_fake.custom_fake = GPIO_PinInit_capture;
led_init();
}
void tearDown(void)
{
}
/* ── Тесты инициализации ─────────────────────────────────────────────── */
void test_led_init_calls_gpio_init_for_each_led(void)
{
TEST_ASSERT_EQUAL(2, GPIO_PinInit_fake.call_count);
}
void test_led_init_configures_as_output(void)
{
/* s_captured_cfg — копия по значению, стек led_init() не нужен */
TEST_ASSERT_EQUAL(kGPIO_DigitalOutput, s_captured_cfg[0].direction);
TEST_ASSERT_EQUAL(kGPIO_DigitalOutput, s_captured_cfg[1].direction);
}
void test_led_init_output_logic_is_high(void)
{
/* active LOW — начальное состояние GPIO = 1 (LED выключен) */
TEST_ASSERT_EQUAL_UINT8(1U, s_captured_cfg[0].outputLogic);
TEST_ASSERT_EQUAL_UINT8(1U, s_captured_cfg[1].outputLogic);
}
void test_led_init_both_leds_off(void)
{
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
TEST_ASSERT_FALSE(led_get(LED_APP));
}
/* ── Тесты led_on / led_off ──────────────────────────────────────────── */
void test_led_on_sets_state_true(void)
{
led_on(LED_HEARTBEAT);
TEST_ASSERT_TRUE(led_get(LED_HEARTBEAT));
}
void test_led_off_sets_state_false(void)
{
led_on(LED_HEARTBEAT);
led_off(LED_HEARTBEAT);
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
}
void test_led_on_writes_gpio_low(void)
{
/* active LOW: включить LED = записать 0 в GPIO */
led_on(LED_HEARTBEAT);
TEST_ASSERT_EQUAL_UINT8(0U, GPIO_PinWrite_fake.arg2_val);
}
void test_led_off_writes_gpio_high(void)
{
/* active LOW: выключить LED = записать 1 в GPIO */
led_off(LED_HEARTBEAT);
TEST_ASSERT_EQUAL_UINT8(1U, GPIO_PinWrite_fake.arg2_val);
}
/* ── Тесты led_toggle ────────────────────────────────────────────────── */
void test_led_toggle_off_to_on(void)
{
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
led_toggle(LED_HEARTBEAT);
TEST_ASSERT_TRUE(led_get(LED_HEARTBEAT));
}
void test_led_toggle_on_to_off(void)
{
led_on(LED_HEARTBEAT);
led_toggle(LED_HEARTBEAT);
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
}
void test_led_toggle_twice_returns_to_initial(void)
{
led_toggle(LED_HEARTBEAT);
led_toggle(LED_HEARTBEAT);
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
}
/* ── Тесты led_set ───────────────────────────────────────────────────── */
void test_led_set_true_turns_on(void)
{
led_set(LED_APP, true);
TEST_ASSERT_TRUE(led_get(LED_APP));
}
void test_led_set_false_turns_off(void)
{
led_set(LED_APP, true);
led_set(LED_APP, false);
TEST_ASSERT_FALSE(led_get(LED_APP));
}
/* ── Тест независимости LED друг от друга ───────────────────────────── */
void test_leds_are_independent(void)
{
led_on(LED_HEARTBEAT);
TEST_ASSERT_TRUE(led_get(LED_HEARTBEAT));
TEST_ASSERT_FALSE(led_get(LED_APP));
led_on(LED_APP);
led_off(LED_HEARTBEAT);
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
TEST_ASSERT_TRUE(led_get(LED_APP));
}
/* ── main ────────────────────────────────────────────────────────────── */
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_led_init_calls_gpio_init_for_each_led);
RUN_TEST(test_led_init_configures_as_output);
RUN_TEST(test_led_init_output_logic_is_high);
RUN_TEST(test_led_init_both_leds_off);
RUN_TEST(test_led_on_sets_state_true);
RUN_TEST(test_led_off_sets_state_false);
RUN_TEST(test_led_on_writes_gpio_low);
RUN_TEST(test_led_off_writes_gpio_high);
RUN_TEST(test_led_toggle_off_to_on);
RUN_TEST(test_led_toggle_on_to_off);
RUN_TEST(test_led_toggle_twice_returns_to_initial);
RUN_TEST(test_led_set_true_turns_on);
RUN_TEST(test_led_set_false_turns_off);
RUN_TEST(test_leds_are_independent);
return UNITY_END();
}

View file

@ -1,34 +0,0 @@
#include "app.h" // Подключение заголовочного файла с тестируемым функционалом
#include "fff.h"
#include "unity.h"
void setUp(void) {
// Инициализация перед каждым тестом (опционально)
}
void tearDown(void) {
// Очистка после теста (опционально)
}
// Тест 1: Проверка сложения
void test_app_add_returns_correct_sum(void) {
TEST_ASSERT_EQUAL_INT(5, foo_add(2, 3));
TEST_ASSERT_EQUAL_INT(0, foo_add(0, 0));
}
// Тест 2: Отрицательные числа
void test_app_add_handles_negative(void) {
TEST_ASSERT_EQUAL_INT(-1, foo_add(-2, 1));
}
// Тест 3: Переполнение (boundary case)
void test_app_add_overflow(void) {
TEST_ASSERT_INT_WITHIN(1, 2147483647, foo_add(2147483646, 1));
}
int main(void) {
UNITY_BEGIN(); // Запуск Unity
RUN_TEST(test_app_add_returns_correct_sum);
RUN_TEST(test_app_add_handles_negative);
RUN_TEST(test_app_add_overflow);
return UNITY_END(); // Отчёт
}

View file

@ -1,26 +0,0 @@
#include "app.h" // Подключение заголовочного файла с тестируемым функционалом
#include "unity.h"
void setUp(void) {
// вызывется перед каждым тестом
}
void tearDown(void) {
// после каждого теста
}
void test_add_returns_correct_sum(void) {
TEST_ASSERT_EQUAL_INT(5, foo_add(2, 3));
}
void test_add_handles_negative(void) {
TEST_ASSERT_EQUAL_INT(1, foo_add(3, -2));
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_add_returns_correct_sum);
RUN_TEST(test_add_handles_negative);
return UNITY_END();
}

32
tests/target/README.md Normal file
View file

@ -0,0 +1,32 @@
# HIL Testing Toolchain — MIMXRT1052
> Документ описывает архитектуру и инструментальный стек для Hardware-in-the-Loop (HIL) тестирования на базе NXP MIMXRT1052. Тесты запускаются на рабочих станциях разработчиков, оркестрация — из DevContainer.
---
## Оборудование
| Компонент | Роль |
| :--- | :--- |
| NXP MCU-Link | Отладчик + VCOM-мост (один USB-кабель) |
| MIMXRT1052 (таргет) | Целевое устройство |
MCU-Link предоставляет два логических канала по одному USB:
- **CMSIS-DAP** — прошивка и сброс таргета (control plane)
- **VCOM (USB-UART)** — тестовый вывод с таргета (data plane)
---
## Инструментальный стек
| Задача | Инструмент |
| :--- | :--- |
| Прошивка и сброс таргета | `pyocd` Python API |
| Проброс зонда в DevContainer | `pyocd server --allow-remote` по TCP |
| Чтение тестового вывода | `pyserial` (VCOM) или `socat` TCP-мост |
| Оркестрация HIL-тестов | `pytest` + фикстуры в `conftest.py` |
| Сборка C-кода тестовой прошивки | `CMake` (вызывается из pytest fixture) |
| Тестовый фреймворк в прошивке | Unity + fff |
| Unit-тесты (без железа) | CMake + `CTest` (в DevContainer, без таргета) |
| Интерактивная отладка | `pyocd gdbserver` + VS Code **cortex-debug** |