版本:v1.0 | 日期:2026-07-03
本规范规定 xt-sdk 测试代码的编写标准。所有新增测试必须遵循本规范。
配套阅读:编码规范(§14 测试代码规范)、代码注释指南。
一、文件组织
1.1 文件头
测试文件头使用 @details 描述覆盖范围(必需),格式与编码规范 §1.2 一致:
/**
* @file xt_main.c
* @author your name (you@domain.com)
* @brief xt_vfs 单元测试套件——覆盖 init/deinit、mount/unmount、open/close 等 71 项测试
* @details 每个测试用例通过 setUp/tearDown 管理环境初始化和清理。基于 Unity 测试框架运行。
* @version 0.1
* @date 2026-07-03
*
* SPDX-FileCopyrightText: 2026 深圳市天工聚创科技有限公司
* SPDX-License-Identifier: Apache-2.0
*
*/
1.2 include 顺序
标准库 → Unity → 平台基础头 → 依赖模块 → 被测模块:
#include <string.h>
#include <stdio.h>
#include "unity.h"
#include "xtiny.h"
#include "xt_task.h"
#include "xt_vfs.h"
**规则**:
- 标准库( <...> )在最前面
- Unity 和平台基础头紧跟标准库
- 被测模块头文件放在最后
1.3 全局变量
文件作用域的静态变量使用 s_ 前缀。回调间共享的状态使用 volatile :
static xt_vfs_config_t s_cfg;
static volatile int s_subscribe_count;
static volatile int s_unsubscribe_count;
二、setUp / tearDown
2.1 格式
setUp 和 tearDown 使用 @name 分组,格式固定:
/** @name setUp / tearDown */
/** @{ */
/**
* @brief 每个测试用例执行前初始化环境
*/
void setUp(void)
{
// 1. 重置状态
// 2. 初始化模块
// 3. 确认初始化成功
}
/**
* @brief 每个测试用例执行后清理
*/
void tearDown(void)
{
// 清理临时文件/资源
}
/** @} */
2.2 职责划分
| 函数 | 职责 | 说明 |
| setUp | 重置全局状态 → 初始化模块 → 断言 init 成功 | 确保每个测试从干净状态开始 |
| tearDown | 清理临时资源、反初始化 | 可以为空(当 setUp 已充分重置时) |
2.3 关键规则
- setUp 和 tearDown 不加 static (Unity 框架要求)
- setUp 和 tearDown 放在辅助函数之后、测试分组之前
- 如果模块需要 deinit,应在 tearDown 中调用或在 setUp 开头先清理旧实例
三、测试分组
3.1 @name 格式
测试按功能分组的统一格式:
/** @name 1. init/deinit (3 项测试) */
/** @{ */
/**
* @brief 正常初始化后 is_active 应为 true
*/
static void test_init_ok(void)
{
xt_vfs_err_t ret = xt_vfs_init(&s_cfg);
TEST_ASSERT_EQUAL(XT_VFS_ERR_OK, ret);
TEST_ASSERT_TRUE(xt_vfs_is_active());
xt_vfs_deinit();
}
/** @} */
**规则**:
- 使用 N. 中文分组名 (N 项测试) 格式
- 数字编号从 1 递增
- @{ 和 @} 配对使用
- 每个测试函数前必须有 @brief 注释
3.2 辅助函数
测试辅助函数也作为独立分组放在 setUp 之前:
/** @name 辅助函数 */
/** @{ */
static void _write_file(const char *path, const void *data, xt_vfs_size_t size)
{
/* ... */
}
/** @} */
四、测试函数命名
4.1 命名规则
test_ 前缀 + snake_case + 语义化描述:
- test_<api>_<scenario>:如 test_init_ok、test_mount_ok
- test_<scenario>_<expected>:如 test_generation_rejects_old_fd、test_double_init_returns_busy
- 看函数名即能理解测试意图
4.2 示例
static void test_init_ok(void); // 正常初始化
static void test_double_init(void); // 重复初始化
static void test_open_read_write_then_read_back(void); // 读写回读
static void test_null_path_open(void); // NULL 路径错误处理
static void test_write_null_buffer(void); // NULL 缓冲区错误处理
static void test_fd_generation_rejects_old(void); // generation 防悬空
五、测试函数内结构
5.1 三段式结构
每个测试函数按 Given / When / Then 三段组织:
/**
* @brief 正常打开文件后 is_active 应为 true,返回有效 fd
*/
static void test_open_ok(void)
{
// Given: 初始化环境,准备测试数据
const char *path = "/native/test_open_ok.txt";
xt_vfs_fd_t fd;
// When: 执行被测试操作
fd = xt_vfs_open(path, XT_VFS_O_CREAT | XT_VFS_O_RDWR | XT_VFS_O_TRUNC);
// Then: 验证结果
TEST_ASSERT_NOT_EQUAL(XT_VFS_INVALID_FD, fd);
// Cleanup
xt_vfs_close(fd);
xt_vfs_remove(path);
xt_printf("[LOG] test_open_ok: fd=%d\n", fd);
}
**规则**:
- // Given :准备输入、初始化状态
- // When :调用被测 API
- // Then :断言验证,可有多组断言
- // Cleanup :释放资源(如有)
- 函数末尾可以用 xt_printf 输出关键变量值供调试(非强制)
5.2 @brief 写法
@brief 用一句话描述测试场景和期望结果,含被测 API 名:
六、断言宏
6.1 常用断言
| 断言宏 | 用途 | 示例 |
| TEST_ASSERT_EQUAL(expected, actual) | 整数/枚举值比较 | TEST_ASSERT_EQUAL(XT_EOK, ret) |
| TEST_ASSERT_NOT_EQUAL(expected, actual) | 不等于,常用于 fd 有效性 | TEST_ASSERT_NOT_EQUAL(XT_VFS_INVALID_FD, fd) |
| TEST_ASSERT_TRUE(condition) | 布尔值为真 | TEST_ASSERT_TRUE(xt_vfs_is_active()) |
| TEST_ASSERT_FALSE(condition) | 布尔值为假 | TEST_ASSERT_FALSE(xt_vfs_is_active()) |
| TEST_ASSERT_NULL(ptr) | 指针为空 | TEST_ASSERT_NULL(ptr) |
| TEST_ASSERT_NOT_NULL(ptr) | 指针非空 | TEST_ASSERT_NOT_NULL(ptr) |
| TEST_ASSERT_EQUAL_STRING(expected, actual) | 字符串比较 | TEST_ASSERT_EQUAL_STRING("hello", buf) |
| TEST_ASSERT_EQUAL_MEMORY(expected, actual, len) | 二进制比较 | TEST_ASSERT_EQUAL_MEMORY(data, buf, 5) |
6.2 整数范围断言
6.3 选择原则
七、入口函数
7.1 标准格式
/**
* @brief Unity 测试入口
*
* 测试分组顺序:
* 1. init/deinit (3)
* 2. mount/unmount (5)
* 3. open/close (8)
* ...
*
* 共计 71 项测试。
*/
int xt_main(void)
{
UNITY_BEGIN();
/* 1. init/deinit */
RUN_TEST(test_init_ok);
RUN_TEST(test_double_init);
RUN_TEST(test_deinit_resets_state);
/* 2. mount/unmount */
RUN_TEST(test_mount_ok);
/* ... */
return UNITY_END();
}
**规则**:
- 函数名固定为 xt_main(Unity 框架要求)
- 注释中列出所有分组及项数、总计
- RUN_TEST 按分组顺序排列,用 /* N. 分组名 */ 注释分隔
- RUN_TEST 参数是函数名,不加 &
7.2 条件编译
某些测试可用 #if 包裹:
#if XT_VFS_USE_HEAP
/* 3. 堆模式 */
RUN_TEST(test_heap_alloc);
#endif
八、xmake.lua 模板
8.1 最小模板
local TARGET_NAME = "xt_main"
target(TARGET_NAME)
set_kind("static")
add_deps("xt_core")
add_deps("xt_unity")
add_files("xt_main.c")
target_end()
8.2 需额外依赖时
target(TARGET_NAME)
set_kind("static")
add_deps("xt_core")
add_deps("xt_unity")
add_deps("xt_vfs") -- 被测模块
add_deps("xt_vfs_native") -- 测试用的后端
add_files("xt_main.c")
target_end()
8.3 构建与运行
cd tests/<模块名>
xt --target windows/simulator fullclean build
xt --target windows/simulator run
九、测试覆盖场景
9.1 场景分类
测试应覆盖以下场景类型,按优先级排列:
| 优先级 | 场景类型 | 说明 | 示例 |
| 必须 | 正常路径 | 标准输入的标准行为 | test_init_ok、test_write_then_read_back |
| 必须 | 错误处理 | NULL 参数、无效句柄、不存在的资源、未初始化 | test_null_path_open、test_close_invalid_fd |
| 必须 | 边界条件 | 空文件、零长度、路径边界、最大/最小值 | test_read_empty_file、test_start_int32max |
| 推荐 | 幂等/重复 | 重复 init、重复 subscribe、重复 stop | test_double_init、test_stop_idempotent |
| 推荐 | 重入安全 | 回调中操作自身/其他对象、嵌套调用 | test_cb_delete_self、test_cb_start_other |
| 推荐 | 资源耗尽 | 表满、slot 复用 | test_open_file_table_full、test_mount_table_full |
| 推荐 | 状态转换 | 状态机正确性 | test_is_running_false_after_stop |
| 按需 | 防悬空/UAF | generation 校验、bitmap 清除 | test_generation_rejects_old_fd |
| 按需 | 并发/隔离 | 多后端隔离、多事件隔离 | test_two_backends_independent |
| 按需 | 回绕安全 | 整数回绕、tick 溢出 | test_remain_wraparound |
| 按需 | 深度/容量限制 | 嵌套限制、队列溢出 | test_nested_publish_depth_limit |
9.2 递进式测试策略
- 先测**生命周期**(init → deinit)
- 再测**基本功能**(open/read/write/close)
- 然后测**边界和错误**(NULL、空、满、无效值)
- 最后测**重入和复杂场景**(回调中操作、嵌套、并发)
十、多配置自动化测试
10.1 run_tests.py 模式
通过 Python 脚本驱动多配置 xmake 测试(参考 tests/xt_vfs/run_tests.py ):
configs = [
{"desc": "默认全功能", "env": {}},
{"desc": "静态内存", "env": {"XT_VFS_USE_HEAP": "0"}},
{"desc": "最小裸机", "env": {"XT_VFS_USE_HEAP": "0", "XT_VFS_ENABLE_THREAD_SAFE": "0"}},
]
for config in configs:
set_env(config["env"])
run("fullclean build")
run("run")
配合 xmake.lua 中的 set_config 使用:
if os.getenv("XT_VFS_USE_HEAP") then
add_defines("XT_VFS_USE_HEAP=" .. os.getenv("XT_VFS_USE_HEAP"))
end
十一、doxygen snippet 标记
11.1 用途
当 T3 文档需要引用测试代码中的反模式示例时,用 //! [tag] 标记代码片段:
// 在测试代码中标记:
//! [anti_pattern_callback_stop]
void bad_callback(void *arg) {
xt_timer2_t *timer = (xt_timer2_t *)arg;
xt_timer2_stop(timer, true); // 错误:回调中同步释放
}
//! [anti_pattern_callback_stop]
11.2 在文档中引用
\snippet tests/xt_timer2/xt_main.c anti_pattern_callback_stop
标记名使用 snake_case,清晰描述 snippet 的用途。
十二、回调测试模式
12.1 前向声明回调
回调中需要引用静态函数时,在文件顶部前向声明:
static void on_subscribe(void *param, void *user_data);
static void on_event_a(void *param, void *user_data);
12.2 跨回调计数
使用 static volatile 变量在回调间传递计数信息:
static volatile int s_subscribe_count;
static volatile int s_callback_count;
static void on_event(void *param, void *user_data)
{
s_callback_count++;
xt_printf("[LOG] on_event: count=%d\n", s_callback_count);
}
12.3 常见回调测试场景
| 场景 | 测试要点 |
| 回调删除自身 | delete(self) 后不访问已释放内存 |
| 回调操作其他对象 | delete(other) 或 start(other) 的隔离性 |
| 回调中重新订阅 | 订阅后当前轮次不被调用(快照一致性) |
| 回调嵌套 | 嵌套深度限制和位图隔离 |
十三、相关文档