# 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 \
|
||||
curl git cmake ninja-build bear \
|
||||
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 \
|
||||
build-essential \
|
||||
&& update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-17 100 \
|
||||
|
|
|
|||
|
|
@ -128,8 +128,7 @@
|
|||
"displayName": "Host Tests — Debug",
|
||||
"configurePreset": "host-debug",
|
||||
"targets": [
|
||||
"test_host_simple",
|
||||
"test_host_second"
|
||||
"test_bsp_led"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -137,8 +136,7 @@
|
|||
"displayName": "Host Tests — Release",
|
||||
"configurePreset": "host-release",
|
||||
"targets": [
|
||||
"test_host_simple",
|
||||
"test_host_second"
|
||||
"test_bsp_led"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ add_library(
|
|||
generated/board.c generated/pin_mux.c generated/clock_config.c
|
||||
generated/startup/startup_MIMXRT1052.S)
|
||||
|
||||
add_subdirectory(led)
|
||||
|
||||
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки
|
||||
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
|
||||
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 таргеты
|
||||
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 "clock_config.h"
|
||||
#include "pin_mux.h"
|
||||
|
||||
void BOARD_Init(void) {
|
||||
BOARD_InitPins();
|
||||
BOARD_BootClockRUN();
|
||||
void board_hw_init(void)
|
||||
{
|
||||
BOARD_InitPins();
|
||||
BOARD_BootClockRUN();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
/*! @brief The board name */
|
||||
#define BOARD_NAME "TFT4.3_UB_BOARD"
|
||||
|
||||
void BOARD_Init(void);
|
||||
|
||||
/*! //TODO: @brief The board name */
|
||||
void board_hw_init(void);
|
||||
|
||||
#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(
|
||||
firmware_test
|
||||
PRIVATE bsp_board
|
||||
# SDK драйверы периферии — добавляй по мере написания тестов
|
||||
sdk_lpuart # UART тест
|
||||
sdk_flexcan # CAN тест
|
||||
sdk_gpio # GPIO тест (кнопки, светодиоды, входы)
|
||||
sdk_usdhc # uSD тест
|
||||
sdk_semc # SDRAM тест
|
||||
PRIVATE bsp_board bsp_led
|
||||
# Опциональные библиотеки
|
||||
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 "fsl_gpio.h"
|
||||
#include "pin_mux.h"
|
||||
|
||||
/*******************************************************************************
|
||||
* Definitions
|
||||
******************************************************************************/
|
||||
|
||||
/*******************************************************************************
|
||||
* Prototypes
|
||||
******************************************************************************/
|
||||
|
||||
/*******************************************************************************
|
||||
* Variables
|
||||
******************************************************************************/
|
||||
|
||||
/*******************************************************************************
|
||||
* Code
|
||||
******************************************************************************/
|
||||
#include "bsp/led.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
BOARD_Init();
|
||||
GPIO_PinWrite(BOARD_INITPINS_UserLed1_PORT, BOARD_INITPINS_UserLed1_PIN, 0);
|
||||
// GPIO_PinWrite(BOARD_INITPINS_UserLed2_PORT, BOARD_INITPINS_UserLed2_PIN, 0);
|
||||
board_hw_init();
|
||||
led_init();
|
||||
|
||||
led_on(LED_HEARTBEAT);
|
||||
led_off(LED_APP);
|
||||
while (1)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ endif()
|
|||
# === FFF Mocking Framework ===
|
||||
if(UNITY_TESTING_ENABLED)
|
||||
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)
|
||||
message(STATUS "FFF ✅")
|
||||
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)
|
||||
add_executable(${TEST_NAME} ${TEST_SOURCES})
|
||||
# tests/host/CMakeLists.txt
|
||||
|
||||
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(
|
||||
NAME ${TEST_NAME}
|
||||
COMMAND ${TEST_NAME}
|
||||
NAME ${ARG_NAME}
|
||||
COMMAND ${ARG_NAME}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
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
|
||||
# ${PROJECT_SOURCE_DIR}/lib/hal/src/app.c)
|
||||
set(BSP_MOCKS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mocks)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 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