Zephyr2.7.0与3.7.0测试用例版本差异

一、结构与设计思想的不同

1. 2.7.0 (基于 test_main)

  • 设计特点
    • test_main是一个集中式的入口函数,所有的测试用例通过注册方式放在一个全局范围。
    • 需要手动维护测试函数列表和初始化代码。
  • 局限性
    • 缺乏模块化和灵活性,复杂测试场景下代码难以管理。
    • 没有专门的机制支持前置与后置操作(setup 和 teardown)。

2. 3.7.0 (基于 ZTEST)

  • 设计特点
    • 引入了模块化的测试套件框架,每个测试套件通过宏进行定义。
    • 支持suite_name、before_fn、after_fn、before_each_fn、after_each_fn及suite_data。
    • 自动管理测试用例的注册和执行,提供更清晰的结构。
  • 优势
    • 提高了代码的可维护性和可读性。
    • 更易于扩展和集成,比如为每个测试用例定义不同的前后处理逻辑。

二、代码示例对比

1. 2.7.0示例 (基于 test_main)

c 复制代码
#include <ztest.h>

/* 测试用例函数 */
void test_case1(void) {
    zassert_equal(1, 1, "Values are not equal");
}

void test_case2(void) {
    zassert_not_null(NULL, "Pointer is NULL");
}

/* 测试套件入口函数 */
void test_main(void) {
    /* 注册测试用例 */
    ztest_test_suite(my_test_suite,
        ztest_unit_test(test_case1),
        ztest_unit_test(test_case2)
    );

    /* 运行测试套件 */
    ztest_run_test_suite(my_test_suite);
}
  • 特点
    • 所有测试函数都集中在test_main中注册和运行。
    • 缺少前后处理逻辑(如测试初始化和资源清理)。

2. 3.7.0示例 (基于 ZTEST)

c 复制代码
#include <zephyr/ztest.h>

/* 前置操作 (Setup) */
static void setup(void *data) {
    printk("Setup for test suite\\n");
}

/* 后置操作 (Teardown) */
static void teardown(void *data) {
    printk("Teardown for test suite\\n");
}

/* 测试用例函数 */
ZTEST(my_test_suite, test_case1) {
    zassert_equal(1, 1, "Values are not equal");
}

ZTEST(my_test_suite, test_case2) {
    zassert_not_null(NULL, "Pointer is NULL");
}

/* 定义测试套件 */
ZTEST_SUITE(my_test_suite, NULL, setup, NULL, teardown, NULL);
  • 特点
    • 使用ZTEST_SUITE定义测试套件并绑定setup和teardown。
    • 测试用例使用ZTEST(套件名, 测试函数名)宏定义,自动注册。
    • 提供清晰的模块化结构,适合复杂项目。

三、高级特性对比

1. 头文件引用差异

c 复制代码
// 2.7.0 版本
#include <ztest.h>
#include <kernel.h>
#include <drivers/uart.h>

// 3.7.0 版本
#include <zephyr/ztest.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>

2. 测试用例组织

c 复制代码
// 2.7.0 版本 - 通过函数组织
static void test_uart_basic(void) {
    // 测试代码
}

// 3.7.0 版本 - 通过宏组织
ZTEST(uart_tests, test_uart_basic) {
    // 测试代码
}

3. 生命周期管理

c 复制代码
// 2.7.0 版本
ztest_unit_test_setup_teardown(test_func, setup_fn, teardown_fn);

// 3.7.0 版本
ZTEST_SUITE(test_suite, NULL,
    setup_fn,      /* 套件启动前 */
    before_fn,     /* 每个测试前 */
    after_fn,      /* 每个测试后 */
    teardown_fn    /* 套件结束后 */
);

四、区别总结表

特性 2.7.0 (test_main) 3.7.0 (ZTEST)
测试用例注册方式 手动注册 自动注册,使用ZTEST宏定义
模块化支持 较弱,所有代码集中在test_main 强,测试套件分离,便于扩展
前后处理 (setup/teardown) 无,需手动在测试函数中实现 原生支持,通过ZTEST_SUITE绑定
代码结构可读性 较差,复杂项目中难以管理 高度清晰,支持复杂项目
API 灵活性 基础的断言和测试功能 丰富的断言和模拟功能
适用场景 小型简单的测试代码 复杂的测试场景,尤其是需要共享状态的场景

五、实际应用场景

1. 2.7.0 test_main 适用场景

  • 小型项目或简单测试,测试用例数量较少。
  • 没有复杂的前置或后置需求。
  • 单一功能模块的测试。

2. 3.7.0 ZTEST 适用场景

  • 大型项目,测试用例多,结构复杂。
  • 需要模块化管理、前置/后置逻辑和共享状态的场景。
  • 更易与自动化测试工具(如Twister)集成。
  • 需要高度可维护性的测试项目。

六、升级建议

1. 渐进式迁移步骤

  1. 更新头文件包含方式
  2. 将测试用例函数改写为ZTEST格式
  3. 添加生命周期管理函数
  4. 重构测试套件结构
  5. 优化测试用例组织

2. 注意事项

  • 保持测试逻辑的一致性
  • 确保所有测试用例都被正确迁移
  • 验证生命周期函数的正确执行
  • 检查测试覆盖率的完整性

七、实际应用案例展示

1. UART驱动测试案例对比

2.7.0版本实现

c 复制代码
/*
 * Copyright (c) 2024 Martin.Ma
 * SPDX-License-Identifier: Apache-2.0
 */
#include <ztest.h>
#include <kernel.h>
#include <device.h>
#include <drivers/uart.h>

/* 设备定义 */
#if DT_NODE_HAS_STATUS(DT_CHOSEN(zephyr_console), okay)
#define UART_NODE DT_CHOSEN(zephyr_console)
#else
#define UART_NODE DT_NODELABEL(uart0)
#endif

static const struct device *uart_dev;

/* 设备准备函数 */
static void uart_setup(void)
{
    uart_dev = DEVICE_DT_GET(UART_NODE);
    zassert_not_null(uart_dev, "UART device not found");
    zassert_true(device_is_ready(uart_dev), "UART device not ready");
}

/* 测试用例:基本发送功能 */
static void test_uart_basic_tx(void)
{
    const char tx_str[] = "Hello UART\\r\\n";
    TC_PRINT("Testing basic UART transmission\\n");
    TC_PRINT("Sending: %s", tx_str);

    /* 发送测试字符串 */
    for (int i = 0; i < strlen(tx_str); i++) {
        uart_poll_out(uart_dev, tx_str[i]);
        k_sleep(K_MSEC(1));  /* 小延时确保稳定性 */
    }

    /* 这里我们只验证发送调用是否成功完成 */
    zassert_true(true, "Basic transmission test completed");
}

/* 测试用例:基本接收功能 */
static void test_uart_basic_rx(void)
{
    unsigned char rx_char;
    int ret;
    TC_PRINT("Testing basic UART reception\\n");

    /* 尝试接收一个字符 */
    ret = uart_poll_in(uart_dev, &rx_char);

    /* 验证返回值正确性 */
    zassert_true(ret == 0 || ret == -1,
                "uart_poll_in returned unexpected value: %d", ret);
    if (ret == 0) {
        TC_PRINT("Received character: %c\\n", rx_char);
    } else {
        TC_PRINT("No data available (expected in this test)\\n");
    }
}

/* 测试用例:简单的数据环回测试 */
static void test_uart_loopback(void)
{
    const char test_char = 'A';
    unsigned char rx_char;
    int ret;
    TC_PRINT("Testing UART loopback with single character\\n");

    /* 发送单个字符 */
    TC_PRINT("Sending character: %c\\n", test_char);
    uart_poll_out(uart_dev, test_char);
    k_sleep(K_MSEC(10));  /* 等待数据传输完成 */

    /* 尝试接收字符 */
    ret = uart_poll_in(uart_dev, &rx_char);

    /* 如果成功接收到数据,验证其正确性 */
    if (ret == 0) {
        TC_PRINT("Received character: %c\\n", rx_char);
        zassert_equal(rx_char, test_char,
                     "Character mismatch: expected '%c', got '%c'",
                     test_char, rx_char);
    }
}

/* 主测试函数 */
void test_main(void)
{
    uart_setup();
    ztest_test_suite(uart_pl011_suite,
        ztest_unit_test(test_uart_basic_tx),
        ztest_unit_test(test_uart_basic_rx),
        ztest_unit_test(test_uart_loopback)
    );
    ztest_run_test_suite(uart_pl011_suite);
}

3.7.0版本实现

c 复制代码
/*
 * Copyright (c) 2024 Martin.Ma
 * SPDX-License-Identifier: Apache-2.0
 */
#include <zephyr/ztest.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>

/* 设备定义 */
#if DT_NODE_HAS_STATUS(DT_CHOSEN(zephyr_console), okay)
#define UART_NODE DT_CHOSEN(zephyr_console)
#else
#define UART_NODE DT_NODELABEL(uart0)
#endif

static const struct device *uart_dev;

/* 测试用例:基本发送功能 */
ZTEST(uart_pl011, test_uart_basic_tx)
{
    const char tx_str[] = "Hello UART\\r\\n";
    TC_PRINT("Testing basic UART transmission\\n");
    TC_PRINT("Sending: %s", tx_str);

    /* 发送测试字符串 */
    for (int i = 0; i < strlen(tx_str); i++) {
        uart_poll_out(uart_dev, tx_str[i]);
        k_sleep(K_MSEC(1));  /* 小延时确保稳定性 */
    }

    /* 这里我们只验证发送调用是否成功完成 */
    zassert_true(true, "Basic transmission test completed");
}

/* 测试用例:基本接收功能 */
ZTEST(uart_pl011, test_uart_basic_rx)
{
    unsigned char rx_char;
    int ret;
    TC_PRINT("Testing basic UART reception\\n");

    /* 尝试接收一个字符 */
    ret = uart_poll_in(uart_dev, &rx_char);

    /* 验证返回值正确性 */
    zassert_true(ret == 0 || ret == -1,
                "uart_poll_in returned unexpected value: %d", ret);
    if (ret == 0) {
        TC_PRINT("Received character: %c\\n", rx_char);
    } else {
        TC_PRINT("No data available (expected in this test)\\n");
    }
}

/* 测试用例:简单的数据环回测试 */
ZTEST(uart_pl011, test_uart_loopback)
{
    const char test_char = 'A';
    unsigned char rx_char;
    int ret;
    TC_PRINT("Testing UART loopback with single character\\n");

    /* 发送单个字符 */
    TC_PRINT("Sending character: %c\\n", test_char);
    uart_poll_out(uart_dev, test_char);
    k_sleep(K_MSEC(10));  /* 等待数据传输完成 */

    /* 尝试接收字符 */
    ret = uart_poll_in(uart_dev, &rx_char);

    /* 如果成功接收到数据,验证其正确性 */
    if (ret == 0) {
        TC_PRINT("Received character: %c\\n", rx_char);
        zassert_equal(rx_char, test_char,
                     "Character mismatch: expected '%c', got '%c'",
                     test_char, rx_char);
    }
}

/* 设备准备函数 */
static void *uart_setup(void)
{
    uart_dev = DEVICE_DT_GET(UART_NODE);
    zassert_not_null(uart_dev, "UART device not found");
    zassert_true(device_is_ready(uart_dev), "UART device not ready");
    return NULL;
}

/* 定义测试套件 */
ZTEST_SUITE(uart_pl011, NULL, uart_setup, NULL, NULL, NULL);

2. FIFO/LIFO测试案例

2.7.0版本实现

c 复制代码
#define STACK_SIZE 500
#define LIST_LEN 8

static struct k_sem sema;
static struct k_fifo fifo;
static struct k_thread tdata;
static K_THREAD_STACK_DEFINE(tstack, STACK_SIZE);

struct fifo_item {
    void *fifo_reserved;
    uint8_t value;
};

static struct fifo_item fifo_data[LIST_LEN];

static void fifo_thread_entry(void *p1, void *p2, void *p3)
{
    struct fifo_item *rx_data;

    /* 从FIFO读取数据 */
    for (int i = 0; i < LIST_LEN; i++) {
        rx_data = k_fifo_get(&fifo, K_NO_WAIT);
        zassert_equal(rx_data->value, fifo_data[i].value, NULL);
    }

    k_sem_give(&sema);
}

static void test_fifo(void)
{
    k_sem_init(&sema, 0, 1);
    k_fifo_init(&fifo);

    /* 写入测试数据 */
    for (int i = 0; i < LIST_LEN; i++) {
        fifo_data[i].value = i;
        k_fifo_put(&fifo, &fifo_data[i]);
    }

    /* 创建测试线程 */
    k_tid_t tid = k_thread_create(&tdata, tstack, STACK_SIZE,
                                 fifo_thread_entry, &fifo, NULL, NULL,
                                 K_PRIO_PREEMPT(0), 0, K_NO_WAIT);

    k_sem_take(&sema, K_FOREVER);
    k_thread_abort(tid);
}

void test_main(void)
{
    ztest_test_suite(kernel_tests,
        ztest_unit_test(test_fifo)
    );
    ztest_run_test_suite(kernel_tests);
}

3.7.0版本实现

c 复制代码
#define STACK_SIZE 500
#define LIST_LEN 8

struct fifo_test_fixture {
    struct k_sem sema;
    struct k_fifo fifo;
    struct k_thread tdata;
    struct fifo_item {
        void *fifo_reserved;
        uint8_t value;
    } data[LIST_LEN];
};

static K_THREAD_STACK_DEFINE(tstack, STACK_SIZE);

static void fifo_thread_entry(void *arg)
{
    struct fifo_test_fixture *fixture = (struct fifo_test_fixture *)arg;
    struct fifo_item *rx_data;

    /* 从FIFO读取数据 */
    for (int i = 0; i < LIST_LEN; i++) {
        rx_data = k_fifo_get(&fixture->fifo, K_NO_WAIT);
        zassert_equal(rx_data->value, fixture->data[i].value, NULL);
    }

    k_sem_give(&fixture->sema);
}

static void *fifo_setup(void)
{
    struct fifo_test_fixture *fixture = malloc(sizeof(struct fifo_test_fixture));

    k_sem_init(&fixture->sema, 0, 1);
    k_fifo_init(&fixture->fifo);

    return fixture;
}

static void fifo_before(void *data)
{
    struct fifo_test_fixture *fixture = (struct fifo_test_fixture *)data;

    /* 准备测试数据 */
    for (int i = 0; i < LIST_LEN; i++) {
        fixture->data[i].value = i;
    }
}

static void fifo_teardown(void *data)
{
    free(data);
}

ZTEST_F(kernel, test_fifo)
{
    /* 写入测试数据 */
    for (int i = 0; i < LIST_LEN; i++) {
        k_fifo_put(&fixture->fifo, &fixture->data[i]);
    }

    /* 创建测试线程 */
    k_tid_t tid = k_thread_create(&fixture->tdata, tstack, STACK_SIZE,
                                 fifo_thread_entry, fixture, NULL, NULL,
                                 K_PRIO_PREEMPT(0), 0, K_NO_WAIT);

    k_sem_take(&fixture->sema, K_FOREVER);
    k_thread_abort(tid);
}

ZTEST_SUITE(kernel, NULL, fifo_setup, fifo_before, NULL, fifo_teardown);

这些实际案例展示了两个版本在测试实现上的具体差异:

  1. 数据管理方式的变化:3.7.0版本使用fixture管理测试数据,而2.7.0使用全局变量
  2. 生命周期管理的改进:3.7.0提供了更完整的生命周期管理机制
  3. 代码组织结构的优化:3.7.0的代码结构更清晰、模块化程度更高
  4. 错误处理的增强:3.7.0提供了更多的错误检查点

八、最佳实践建议

1. 测试代码组织

  • 按功能模块组织测试套件
  • 合理使用setup和teardown
  • 保持测试用例的独立性
  • 注意资源的正确清理

2. 异常处理

  • 合理使用断言验证结果
  • 处理所有可能的错误场景
  • 确保资源正确释放

3. 调试技巧

  • 使用适当的日志级别
  • 合理设置断点
  • 验证测试环境的正确性

这个升级过程虽然需要投入一定的工作量,但从长远来看是值得的,因为它能带来更好的代码组织、更强的可维护性和更高的测试效率。建议在进行升级时,采取循序渐进的方式,确保每一步都经过充分的验证。

相关推荐
鲁正杰1 小时前
在一个服务器上抓取 Docker 镜像并在另一个服务器上运行
运维·服务器·docker
F-2H1 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
aherhuo1 小时前
基于openEuler22.09部署OpenStack Yoga云平台(一)
linux·运维·服务器·openstack
WebDeveloper20011 小时前
如何使用美国域名中心US Domain Center和WordPress创建商业网站
运维·服务器·css·网络·html
檀越剑指大厂2 小时前
【Linux系列】Shell 脚本中的条件判断:`[ ]`与`[[ ]]`的比较
linux·运维·服务器
2301_819287124 小时前
ce第六次作业
linux·运维·服务器·网络
CIb0la4 小时前
GitLab 停止为中国区用户提供 GitLab.com 账号服务
运维·网络·程序人生
武汉联从信息4 小时前
如何使用linux日志管理工具来管理oracle osb服务器日志文件?
linux·运维·服务器
天天进步20154 小时前
STUN服务器实现NAT穿透
运维·服务器
月如琉璃4 小时前
1.gitlab 服务器搭建流程
服务器·gitlab