# 3.
- Модуль bsp/led - Шаблон хост-тестов для модуля bsp/led
This commit is contained in:
parent
33aabbcce1
commit
72045dabb3
24 changed files with 1186 additions and 1065 deletions
|
|
@ -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 \
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
17
bsp/led/CMakeLists.txt
Normal 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
70
bsp/led/README.md
Normal 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
49
bsp/led/include/bsp/led.h
Normal 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
101
bsp/led/src/led.c
Normal 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;
|
||||||
|
}
|
||||||
0
bsp/usb_cdc/CMakeLists.txt
Normal file
0
bsp/usb_cdc/CMakeLists.txt
Normal 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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
```
|
|
||||||
312
tests/README.md
312
tests/README.md
|
|
@ -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 |
|
|
||||||
|
|
@ -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
584
tests/host/README.md
Normal 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
2
tests/host/mocks/board.h
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#pragma once
|
||||||
|
/* Stub board.h для host-тестов — led.c включает его транзитивно через bsp_board */
|
||||||
40
tests/host/mocks/fsl_gpio.h
Normal file
40
tests/host/mocks/fsl_gpio.h
Normal 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);
|
||||||
22
tests/host/mocks/pin_mux.h
Normal file
22
tests/host/mocks/pin_mux.h
Normal 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
191
tests/host/test_bsp_led.c
Normal 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();
|
||||||
|
}
|
||||||
|
|
@ -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(); // Отчёт
|
|
||||||
}
|
|
||||||
|
|
@ -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
32
tests/target/README.md
Normal 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** |
|
||||||
Loading…
Reference in a new issue