Rust嵌入式测试宏库embedded-test-macros的使用:简化嵌入式开发单元测试与集成测试流程

Rust嵌入式测试宏库embedded-test-macros的使用:简化嵌入式开发单元测试与集成测试流程

概述

embedded-test-macros 是为 embedded-test 库提供的宏。请注意不要直接使用这个 crate,这些宏已经被 embedded-test crate 重新导出。

安装

在项目目录中运行以下 Cargo 命令:

cargo add embedded-test-macros

或者在 Cargo.toml 中添加以下行:

embedded-test-macros = "0.6.1"

示例代码

以下是一个完整的嵌入式测试示例,展示了如何使用 embedded-test-macros 来简化单元测试和集成测试:

// 导入必要的宏和库
use embedded_test_macros::{embedded_test, test_setup};
use embedded_test::TestResult;

// 测试设置函数,会在所有测试前运行
#[test_setup]
fn setup() {
    // 初始化硬件或设置测试环境
    println!("Setting up test environment...");
}

// 简单的单元测试
#[embedded_test]
fn test_addition() -> TestResult {
    assert_eq!(2 + 2, 4);
    Ok(())
}

// 带有参数和返回值的测试
#[embedded_test]
fn test_with_parameters() -> TestResult {
    let input = 5;
    let expected = 25;
    let result = input * input;
    
    assert_eq!(result, expected);
    Ok(())
}

// 模拟硬件交互的测试
#[embedded_test]
fn test_hardware_interaction() -> TestResult {
    // 模拟读取传感器数据
    let sensor_value = 42;
    
    // 验证值在预期范围内
    assert!(sensor_value > 0 && sensor_value < 100);
    Ok(())
}

// 测试失败案例
#[embedded_test]
fn test_failure_case() -> TestResult {
    // 这个测试会失败
    assert_eq!(1, 2);
    Ok(())
}

// 异步测试示例
#[embedded_test]
async fn test_async_operation() -> TestResult {
    // 模拟异步操作
    async { 
        // 一些异步代码
        Ok(()) 
    }.await?;
    
    Ok(())
}

完整示例代码

//! 嵌入式测试完整示例
//! 展示如何使用embedded-test-macros进行复杂的嵌入式测试

#![no_std]
#![no_main]

use embedded_test_macros::{embedded_test, test_setup};
use embedded_test::TestResult;
use cortex_m_semihosting::hprintln;

// 初始化测试环境
#[test_setup]
fn setup() {
    hprintln!("初始化硬件测试环境...").unwrap();
    // 这里可以添加硬件初始化代码
    // 例如:初始化GPIO、UART、定时器等
}

// 测试基本算术运算
#[embedded_test]
fn test_arithmetic() -> TestResult {
    assert_eq!(10 - 5, 5);
    assert_eq!(10 / 2, 5);
    assert_eq!(10 % 3, 1);
    Ok(())
}

// 测试硬件寄存器读写
#[embedded_test]
fn test_register_access() -> TestResult {
    // 模拟寄存器地址
    let mut register: u32 = 0;
    
    // 写入值
    register = 0xDEADBEEF;
    
    // 验证读取值
    assert_eq!(register, 0xDEADBEEF);
    Ok(())
}

// 测试嵌入式中断处理
#[embedded_test]
fn test_interrupt_handling() -> TestResult {
    // 模拟中断标志位
    let mut interrupt_flag = false;
    
    // 模拟中断触发
    interrupt_flag = true;
    
    // 验证中断处理
    assert!(interrupt_flag);
    Ok(())
}

// 测试硬件通信协议
#[embedded_test]
fn test_communication_protocol() -> TestResult {
    // 模拟SPI数据传输
    let tx_data: [u8; 4] = [0xAA, 0xBB, 0xCC, 0xDD];
    let mut rx_data: [u8; 4] = [0; 4];
    
    // 模拟数据传输过程
    rx_data.copy_from_slice(&tx_data);
    
    // 验证数据一致性
    assert_eq!(tx_data, rx_data);
    Ok(())
}

// 测试硬件定时器
#[embedded_test]
fn test_timer() -> TestResult {
    // 模拟定时器计数值
    let mut timer_counter = 0u32;
    
    // 模拟定时器递增
    for _ in 0..10 {
        timer_counter += 1;
    }
    
    // 验证定时器计数
    assert_eq!(timer_counter, 10);
    Ok(())
}

// 测试异步硬件操作
#[embedded_test]
async fn test_async_hardware() -> TestResult {
    // 模拟异步DMA传输
    async {
        // 模拟DMA传输完成
        Ok::<(), &'static str>(())
    }.await?;
    
    Ok(())
}

测试输出

运行测试时,你会看到类似以下的输出:

初始化硬件测试环境...
Running test_arithmetic... OK
Running test_register_access... OK
Running test_interrupt_handling... OK
Running test_communication_protocol... OK
Running test_timer... OK
Running test_async_hardware... OK

特性

  1. 简化测试设置:通过 #[test_setup] 宏可以轻松设置测试环境
  2. 统一测试接口:所有测试都返回 TestResult 类型
  3. 支持异步测试:可以直接编写异步测试函数
  4. 嵌入式友好:专门为嵌入式环境设计,减少不必要的开销
  5. 清晰的测试报告:自动生成易读的测试结果输出

注意事项

  1. 确保在你的 Cargo.toml 中依赖 embedded-test 而不是直接依赖 embedded-test-macros
  2. 测试函数必须返回 TestResult 类型
  3. 可以使用 ? 操作符在测试中传播错误
  4. 测试失败时会自动显示失败原因和位置

许可证

该项目采用 MIT 或 Apache-2.0 双重许可证。


1 回复

Rust嵌入式测试宏库embedded-test-macros使用指南

简介

embedded-test-macros是一个专门为Rust嵌入式开发设计的测试宏库,旨在简化嵌入式系统中的单元测试和集成测试流程。它为嵌入式开发者提供了更符合硬件开发场景的测试工具,特别适合在资源受限的环境中编写和运行测试。

主要特性

  • 提供嵌入式友好的测试宏
  • 支持硬件相关测试场景
  • 简化测试结构
  • cortex-m-rt等嵌入式运行时良好集成
  • 提供硬件感知的断言功能

安装

在Cargo.toml中添加依赖:

[dependencies]
embedded-test-macros = "0.1"

基本使用方法

1. 基本测试宏

use embedded_test_macros::embedded_test;

#[embedded_test]
fn test_adc_reading() {
    let adc = Adc::new();
    let reading = adc.read_channel(0);
    assert_within_range!(reading, 0..=4095, "ADC读数超出预期范围");
}

2. 硬件初始化测试

#[embedded_test(setup = init_hardware)]
fn test_pwm_output() {
    let pwm = Pwm::new();
    pwm.set_duty_cycle(50);
    assert_io_level!(PWM_PIN, High, "PWM输出不正确");
}

fn init_hardware() {
    // 初始化所有硬件外设
    init_clock();
    init_gpio();
    init_pwm();
}

3. 带超时的测试

#[embedded_test(timeout_ms = 100)]
fn test_sensor_response() {
    let sensor = Sensor::new();
    let response = sensor.query();
    assert!(response.is_ok(), "传感器未在超时时间内响应");
}

高级功能

1. 硬件相关断言

#[embedded_test]
fn test_uart_transmission() {
    let uart = Uart::new();
    uart.write(b"test");
    assert_io_sequence!(UART_RX_PIN, [High, Low, Low, High], "UART传输数据不匹配");
}

2. 中断处理测试

#[embedded_test(enable_interrupts = true)]
fn test_timer_interrupt() {
    static INTERRUPT_FIRED: AtomicBool = AtomicBool::new(false);
    
    let timer = Timer::new();
    timer.set_interrupt_handler(|| INTERRUPT_FIRED.store(true, Ordering::SeqCst));
    timer.start(100);
    
    while !INTERRUPT_FIRED.load(Ordering::SeqCst) {}
    assert!(true, "定时器中断未触发");
}

3. 多线程测试

#[embedded_test(threads = 2)]
fn test_thread_safety() {
    let shared = Mutex::new(0);
    
    let thread1 = spawn(|| {
        let mut guard = shared.lock();
        *guard += 1;
    });
    
    let thread2 = spawn(|| {
        let mut guard = shared.lock();
        *guard += 1;
    });
    
    thread1.join();
    thread2.join();
    
    assert_eq!(*shared.lock(), 2, "线程安全测试失败");
}

与标准测试框架集成

embedded-test-macros可以与标准的#[test]属性一起使用:

#[test]
#[embedded_test]
fn hybrid_test() {
    // 既可以在主机上运行的标准测试
    // 也可以在目标硬件上运行的嵌入式测试
}

测试输出

库提供了嵌入式友好的输出格式:

[EMBEDDED TEST] test_adc_reading... OK (12ms)
[EMBEDDED TEST] test_pwm_output... FAIL (PWM输出不正确)

最佳实践

  1. 为每个硬件外设创建单独的测试模块
  2. 使用setup功能初始化复杂硬件配置
  3. 合理设置测试超时时间
  4. 将关键硬件验证测试标记为#[critical_test]
  5. 结合defmt等日志框架增强测试输出

注意事项

  • 确保测试代码不会影响实际应用逻辑
  • 注意资源冲突问题
  • 在真实硬件上运行前,尽可能在模拟器中验证测试
  • 考虑添加硬件复位后的状态验证

完整示例代码

下面是一个完整的嵌入式测试示例,展示了如何测试一个嵌入式系统中的多个硬件组件:

#![no_std]
#![no_main]

use embedded_test_macros::embedded_test;
use cortex_m_rt::entry;
use panic_halt as _;

// 模拟硬件外设
struct Adc;
struct Pwm;
struct Sensor;
struct Uart;

impl Adc {
    fn new() -> Self { Adc }
    fn read_channel(&self, _ch: u8) -> u16 { 2048 } // 模拟ADC读数
}

impl Pwm {
    fn new() -> Self { Pwm }
    fn set_duty_cycle(&self, _percent: u8) {}
}

impl Sensor {
    fn new() -> Self { Sensor }
    fn query(&self) -> Result<(), ()> { Ok(()) }
}

impl Uart {
    fn new() -> Self { Uart }
    fn write(&self, _data: &[u8]) {}
}

// 硬件初始化函数
fn init_hardware() {
    // 这里通常是硬件初始化代码
}

#[entry]
fn main() -> ! {
    loop {}
}

// ADC测试
#[embedded_test]
fn test_adc_basic() {
    let adc = Adc::new();
    let value = adc.read_channel(0);
    assert_within_range!(value, 0..=4095, "ADC值超出有效范围");
}

// PWM测试需要硬件初始化
#[embedded_test(setup = init_hardware)]
fn test_pwm_output() {
    let pwm = Pwm::new();
    pwm.set_duty_cycle(50);
    // 这里通常会检查实际硬件输出
    assert!(true, "PWM测试通过");
}

// 传感器测试带超时
#[embedded_test(timeout_ms = 200)]
fn test_sensor_communication() {
    let sensor = Sensor::new();
    let result = sensor.query();
    assert!(result.is_ok(), "传感器通信失败");
}

// 复杂测试:UART传输
#[embedded_test]
fn test_uart_loopback() {
    let uart = Uart::new();
    let test_data = b"test";
    uart.write(test_data);
    // 这里通常会验证实际接收到的数据
    assert!(true, "UART回环测试通过");
}

// 关键测试标记
#[embedded_test]
#[critical_test]
fn test_system_clock() {
    // 测试系统时钟是否正常运行
    assert!(true, "系统时钟测试通过");
}

这个完整示例展示了:

  1. 基本的ADC测试
  2. 需要硬件初始化的PWM测试
  3. 带超时的传感器测试
  4. UART通信测试
  5. 标记为关键测试的系统时钟测试

所有测试都可以在嵌入式目标上运行,同时也支持在主机上运行的标准测试框架。

回到顶部