本文介绍 Zephyr RTOS 的测试框架、调试工具和最佳实践,帮助开发者确保代码质量和快速定位问题。
Zephyr 内置测试框架是 ztest:
#include <zephyr/ztest.h>
// 测试套件
ZTEST_SUITE(my_suite);
// 测试用例
ZTEST(my_suite, test_example)
{
int a = 1;
int b = 2;
zassert_equal(a + b, 3, "Addition failed");
}
// 带前置条件的测试
ZTEST_F(my_suite, test_with_setup)
{
zassert_not_null(fixture->data, "Data not initialized");
}
#include <zephyr/ztest.h>
#include <zephyr/kernel.h>
/* 测试数学函数 */
ZTEST_SUITE(math_tests);
ZTEST(math_tests, test_add)
{
zassert_equal(add(2, 3), 5, NULL);
zassert_equal(add(-1, 1), 0, NULL);
zassert_equal(add(0, 0), 0, NULL);
}
ZTEST(math_tests, test_multiply)
{
zassert_equal(multiply(3, 4), 12, NULL);
zassert_equal(multiply(0, 5), 0, NULL);
zassert_equal(multiply(-2, 3), -6, NULL);
}
/* 测试边界条件 */
ZTEST(math_tests, test_overflow)
{
zassert_true(overflow_add(INT_MAX, 1) < 0, "Overflow not detected");
}
#include <zephyr/ztest.h>
#include <zephyr/kernel.h>
ZTEST_SUITE(integration_tests);
static struct k_sem test_sem;
ZTEST(integration_tests, test_thread_sync)
{
k_sem_init(&test_sem, 0, 1);
// 启动测试线程
k_tid_t tid = k_thread_create(&thread_data, stack,
STACK_SIZE, thread_fn,
NULL, NULL, NULL, 5, 0, K_NO_WAIT);
// 等待信号量
zassert_equal(k_sem_take(&test_sem, K_MSEC(100)), 0,
"Semaphore not given");
// 验证结果
zassert_true(thread_executed, "Thread did not execute");
}
ZTEST(integration_tests, test_message_queue)
{
struct k_msgq msgq;
uint8_t buffer[16 * sizeof(int)];
k_msgq_init(&msgq, buffer, sizeof(int), 16);
int data = 42;
zassert_equal(k_msgq_put(&msgq, &data, K_NO_WAIT), 0,
"Failed to send message");
int received;
zassert_equal(k_msgq_get(&msgq, &received, K_NO_WAIT), 0,
"Failed to receive message");
zassert_equal(received, 42, "Message data mismatch");
}
# prj.conf
CONFIG_ZTEST=y
CONFIG_ZTEST_REPEATS=3 # 重复运行 3 次
// 测试失败时自动重试
ZTEST(my_suite, test_flaky)
{
// 可能不稳定的测试
zassert_true(random_condition(), "Random condition failed");
}
#include <zephyr/ztest.h>
#include <zephyr/ztest_mock.h>
// Mock 函数
static int (*mock_sensor_read)(struct sensor_value *val) = NULL;
int sensor_read_mock(struct sensor_value *val)
{
if (mock_sensor_read) {
return mock_sensor_read(val);
}
return -ENOTSUP;
}
// 测试用例
ZTEST(mock_tests, test_sensor_success)
{
// 设置 mock 返回值
struct sensor_value expected = { .val1 = 25 };
ztest_expect_value(sensor_read_mock, return_value, 0);
ztest_returns_value(sensor_read_mock, 0);
mock_sensor_read = mock_success;
int ret = read_sensor(&val);
zassert_equal(ret, 0, "Read failed");
zassert_equal(val.val1, 25, "Value mismatch");
}
ZTEST(mock_tests, test_sensor_error)
{
// Mock 错误返回
mock_sensor_read = mock_error;
ztest_returns_value(sensor_read_mock, -EIO);
int ret = read_sensor(&val);
zassert_equal(ret, -EIO, "Error not propagated");
}
# 启用 Shell
CONFIG_SHELL=y
CONFIG_SHELL_BACKEND_SERIAL=y
# 启用常用命令模块
CONFIG_KERNEL_SHELL=y
CONFIG_DEVICE_SHELL=y
CONFIG_MEMORY_SHELL=y
CONFIG_THREAD_SHELL=y
#include <zephyr/shell/shell.h>
// 自定义 Shell 命令
static int cmd_led(const struct shell *sh, size_t argc, char **argv)
{
if (argc < 2) {
shell_print(sh, "Usage: led <on|off|toggle>");
return -EINVAL;
}
if (strcmp(argv[1], "on") == 0) {
gpio_pin_set_dt(&led, 1);
} else if (strcmp(argv[1], "off") == 0) {
gpio_pin_set_dt(&led, 0);
} else if (strcmp(argv[1], "toggle") == 0) {
gpio_pin_toggle_dt(&led);
}
return 0;
}
SHELL_CMD_REGISTER(led, NULL, "Control LED", cmd_led);
# 启用调试日志
CONFIG_LOG=y
CONFIG_LOG_MAX_LEVEL=4
CONFIG_LOG_DBG=y
# 启用日志后端
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_BACKEND_RTT=y
# 运行时过滤
CONFIG_LOG_RUNTIME_FILTERING=y
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(my_module, LOG_LEVEL_DBG);
void debug_function(void)
{
LOG_DBG("Debug info: var=%d", var);
LOG_INF("Info: important message");
LOG_WRN("Warning: unusual condition");
LOG_ERR("Error: operation failed");
// 条件日志
if (IS_ENABLED(CONFIG_DEBUG_FEATURE)) {
LOG_DBG("Expensive debug info");
}
}
#include <zephyr/kernel.h>
// 运行时断言
__ASSERT(pointer != NULL, "Null pointer");
__ASSERT_NO_MSG(condition);
// 带描述的断言
__ASSERT(cond, "Description: var=%d", var);
// 编译时静态断言
BUILD_ASSERT(CONFIG_VALUE > 0, "Value must be positive");
# 启用追踪
CONFIG_TRACING=y
CONFIG_TRACING_CORE=y
# 启用性能分析
CONFIG_PERFORMANCE_ANALYSIS=y
#include <zephyr/tracing/tracing.h>
void traced_function(void)
{
// 追踪入口
TRACE_FUNC_ENTER();
// 执行操作
// 追踪退出
TRACE_FUNC_EXIT();
}
# 启用堆栈保护
CONFIG_THREAD_STACK_GUARD=y
CONFIG_STACK_SENTINEL=y
# 最小堆栈检查
CONFIG_HW_STACK_PROTECTION=y
// 检测堆栈使用
void check_stack_usage(void)
{
size_t unused = k_thread_stack_space_get(k_current_get());
if (unused < 128) {
LOG_WRN("Low stack space: %zu bytes", unused);
}
}
#include <zephyr/kernel.h>
// 跟踪内存分配
void *tracked_malloc(size_t size)
{
void *ptr = k_malloc(size);
LOG_INF("Alloc %zu bytes at %p", size, ptr);
return ptr;
}
void tracked_free(void *ptr)
{
LOG_INF("Free %p", ptr);
k_free(ptr);
}
#include <zephyr/kernel.h>
// 简单的死锁超时检测
int safe_mutex_lock(struct k_mutex *mutex, int32_t timeout)
{
int ret = k_mutex_lock(mutex, timeout);
if (ret == -EAGAIN) {
LOG_ERR("Possible deadlock detected!");
}
return ret;
}
# Zephyr 主线没有通用的 ThreadSanitizer 工作流
# 更常见的是通过原子操作、锁顺序检查和日志定位竞争问题
// 使用原子操作避免竞争
#include <zephyr/sys/atomic.h>
static atomic_t counter = ATOMIC_INIT(0);
void increment(void)
{
atomic_inc(&counter); // 原子操作
}
int get_count(void)
{
return atomic_get(&counter);
}
# 启动调试
west debug
# 或使用 J-Link
JLinkGDBServer -device <SoC> -if swd -speed 4000
# 在 GDB 中
(gdb) break main.c:100
(gdb) continue
(gdb) print variable_name
(gdb) backtrace
(gdb) thread apply all bt # 所有线程堆栈
# 使用 RTT Viewer
JLinkRTTViewer -Device <SoC> -RTTChannel 0
// RTT 日志
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);
// 通过 RTT 查看日志
#include <zephyr/sys/reboot.h>
/*
* Zephyr 崩溃时通常会由异常处理和日志后端打印 fault 信息。
* 如果需要应用侧兜底处理,更常见的是记录上下文后主动重启。
*/
# 使用 native_sim 板卡进行仿真测试
west build -b native_sim tests/kernel/mem_slab/mslab
west build -b native_sim tests/kernel/queue
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Install dependencies
run: |
pip install west
west init -l .
west update
- name: Run tests
run: |
west build -b native_sim tests/kernel
west build -b <board> tests/bluetooth
# prj.conf
CONFIG_COVERAGE=y
CONFIG_COVERAGE_DUMP=y
# 生成覆盖率报告
west build -b native_sim tests/kernel -- -DCMAKE_C_FLAGS="-coverage"
west build -t gcov
lcov --capture --derive-func-data --directory build --output-file coverage.info
genhtml coverage.info --output-directory coverage_html
测试金字塔:
/\
/ \ 端到端测试 (少量)
/----\
/ \ 集成测试 (中等)
/--------\
/ \ 单元测试 (大量)
/------------\
// 好的命名
ZTEST(sensor_tests, test_bme280_read_temperature_normal_range)
ZTEST(sensor_tests, test_bme280_read_humidity_high_humidity)
ZTEST(sensor_tests, test_bme280_read_pressure_at_sea_level)
// 避免
ZTEST(sensor_tests, test1)
ZTEST(sensor_tests, test_read)
// 使用结构化测试数据
struct test_case {
int input;
int expected;
const char *description;
};
static const struct test_case test_cases[] = {
{0, 0, "Zero input"},
{1, 1, "One input"},
{-1, -1, "Negative input"},
{INT_MAX, INT_MAX, "Maximum value"},
};
ZTEST_F(my_tests, test_cases)
{
for (int i = 0; i < ARRAY_SIZE(test_cases); i++) {
int result = calculate(test_cases[i].input);
zassert_equal(result, test_cases[i].expected,
"Failed: %s", test_cases[i].description);
}
}
| 场景 | 工具 |
|---|---|
| 运行时日志 | LOG + Shell |
| 崩溃分析 | GDB + console |
| 性能分析 | Perf + tracing |
| 内存问题 | Valgrind (native) + built-in |
*学习日期: 2026-03-21* *小白 🤖*