Rust嵌入式测试框架embedded-test的使用,高效轻量级的嵌入式系统测试工具

以下是基于您提供内容的完整示例和说明:

Rust嵌入式测试框架embedded-test的使用,高效轻量级的嵌入式系统测试工具

框架概述

embedded-test是为嵌入式系统(riscv、arm和xtensa)设计的测试框架,与probe-rs工具配合使用,可在实际设备上运行集成测试。测试流程包括:

  1. 通过probe-rs run烧录测试程序
  2. 通过半主机模式获取测试信息
  3. 逐个执行测试用例(每次重置设备)
  4. 报告最终结果

核心特性

  • 隔离测试:每个用例独立运行,自动重置设备
  • 状态传递:支持初始化函数传递测试状态
  • 异步支持:可选embassy特性支持异步测试
  • 丰富属性:支持should_panic/ignore/timeout等测试属性

完整配置示例

  1. Cargo.toml配置:
[package]
name = "embedded-test-demo"
version = "0.1.0"

[dev-dependencies]
embedded-test = { version = "0.6.0", features = ["log"] }
rtt-target = { version = "0.3.1" }

[[test]]
name = "hw_test"
harness = false
  1. build.rs配置:
fn main() {
    println!("cargo::rustc-link-arg-tests=-Tembedded-test.x");
}
  1. 测试代码示例(main.rs):
#![no_std]
#![no_main]

use core::panic::PanicInfo;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

#[cfg(test)]
#[embedded_test::tests]
mod tests {
    use cortex_m::asm;
    
    // 同步初始化函数
    #[init]
    fn init() -> u32 {
        42 // 返回测试用的魔数
    }

    // 基础测试用例
    #[test]
    fn basic_check() {
        assert_eq!(2 + 2, 4);
    }

    // 带状态的测试
    #[test]
    fn state_test(input: u32) {
        assert_eq!(input, 42);
    }

    // 日志测试(需要log特性)
    #[test]
    #[cfg(feature = "log")]
    fn test_with_logging() {
        rtt_target::rtt_init_print!();
        log::info!("Test log output");
        assert!(true);
    }

    // 超时测试(10秒超时)
    #[test]
    #[timeout(10)]
    fn long_running_test() {
        for _ in 0..100_000 {
            asm::nop();
        }
    }

    // 预期panic的测试
    #[test]
    #[should_panic]
    fn expected_failure() {
        panic!("This should be caught");
    }
}
  1. 运行配置(.cargo/config.toml):
[target.thumbv7em-none-eabihf]
runner = "probe-rs run --chip STM32F767ZITx"

执行流程

  1. 安装工具链:
cargo install probe-rs-tools
rustup target add thumbv7em-none-eabihf
  1. 运行测试:
cargo test --test hw_test --features log

特性说明

特性名 作用描述
defmt 通过defmt输出测试结果
embassy 启用异步测试支持
xtensa-semihosting 为XTensa架构启用半主机模式

该框架特别适合需要硬件在环测试的嵌入式开发场景,通过实际硬件验证确保代码可靠性。


1 回复

Rust嵌入式测试框架embedded-test的使用

介绍

embedded-test是一个专为Rust嵌入式开发设计的高效、轻量级测试框架。它为嵌入式系统提供了简单易用的测试工具,特别适合在资源受限的环境中进行单元测试和集成测试。

主要特点:

  • 轻量级设计,适合资源受限的嵌入式环境
  • 支持裸机(no_std)环境
  • 提供基本的断言功能
  • 可定制的测试输出
  • 与cargo test集成

使用方法

1. 添加依赖

首先在Cargo.toml中添加依赖:

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

2. 基本测试示例

#![no_std]
#![no_main]

use embedded_test::runner::test_runner;
use embedded_test::println;

#[test_case]
fn test_addition() {
    assert_eq!(2 + 2, 4);  // 基本数学运算测试
}

#[test_case]
fn test_memory_allocation() {
    let x = 42;  // 简单内存分配测试
    assert_ne!(x, 0);
}

#[entry]
fn main() -> ! {
    test_runner();  // 运行所有测试用例
    loop {}
}

3. 自定义测试运行器

use embedded_test::{
    runner::TestRunner,
    printer::SerialPortPrinter,
    TestResult
};

// 自定义测试运行器,使用串口输出结果
fn custom_test_runner(tests: &[&dyn Fn() -> TestResult]) {
    let mut printer = SerialPortPrinter::new(0xE0000000 as *mut u8);
    let mut runner = TestRunner::new(&mut printer);
    
    runner.run_tests(tests);  // 运行所有测试
}

#[entry]
fn main() -> ! {
    // 指定要运行的测试用例
    custom_test_runner(&[&test_addition, &test_memory_allocation]);
    loop {}
}

4. 硬件相关测试示例

#[test_case]
fn test_led_blink() {
    use embedded_hal::digital::v2::OutputPin;
    
    let mut led = Led::new();
    led.set_high().unwrap();  // 点亮LED
    assert_eq!(led.is_high(), true);  // 验证LED状态
    
    led.set_low().unwrap();  // 关闭LED
    assert_eq!(led.is_low(), true);  // 再次验证LED状态
}

#[test_case]
fn test_adc_reading() {
    let adc = Adc::new();
    // 读取ADC通道0的值
    let reading = adc.read(Channel::Channel0).unwrap();
    // 验证ADC读数在合理范围内
    assert!(reading > 0 && reading < 4096, "ADC reading out of range");
}

5. 测试组织

mod sensor_tests {
    use super::*;
    
    #[test_case]
    fn test_temperature_sensor() {
        let temp = read_temperature();  // 读取温度传感器
        // 验证温度在合理范围内
        assert!(temp > -40.0 && temp < 85.0, "Temperature out of expected range");
    }
}

mod communication_tests {
    use super::*;
    
    #[test_case]
    fn test_i2c_communication() {
        let mut i2c = I2c::new();
        // 测试I2C通信
        let result = i2c.write(0x42, &[0x01, 0x02]);
        assert!(result.is_ok(), "I2C communication failed");
    }
}

高级用法

1. 测试夹具

// 测试夹具结构体
struct TestFixture {
    peripheral: SomePeripheral,
}

impl TestFixture {
    fn new() -> Self {
        Self {
            peripheral: SomePeripheral::initialize(),  // 初始化外设
        }
    }
}

#[test_case]
fn test_with_fixture() {
    let fixture = TestFixture::new();  // 创建测试夹具
    // 使用夹具中的外设进行操作
    let result = fixture.peripheral.do_something();
    assert!(result.is_ok());  // 验证操作结果
}

2. 模拟时间测试

#[test_case]
fn test_timeout() {
    use embedded_time::duration::Milliseconds;
    
    let start = get_system_tick();  // 获取开始时间
    delay(Milliseconds(500));  // 延迟500ms
    let elapsed = get_system_tick() - start;  // 计算经过时间
    
    // 验证延迟时间在合理范围内
    assert!(
        elapsed >= Milliseconds(500) && elapsed <= Milliseconds(520),
        "Delay not within expected range"
    );
}

3. 测试失败处理

#[test_case]
fn test_error_handling() {
    let result = risky_operation();  // 执行可能失败的操作
    if let Err(e) = result {
        println!("Test failed with error: {:?}", e);  // 输出错误信息
        assert!(false, "Operation failed with error");  // 标记测试失败
    }
}

完整示例demo

下面是一个完整的嵌入式测试项目示例,展示了如何使用embedded-test框架:

#![no_std]
#![no_main]

use embedded_test::{
    runner::test_runner,
    println,
    TestResult
};
use cortex_m_rt::entry;
use panic_halt as _;

// 简单数学运算测试
#[test_case]
fn test_math_operations() {
    assert_eq!(1 + 1, 2);
    assert_ne!(5 * 5, 10);
}

// 硬件抽象层测试
#[test_case]
fn test_hal_operations() {
    // 假设我们有一个模拟的GPIO实现
    let mut pin = MockGpio::new();
    pin.set_high().unwrap();
    assert!(pin.is_high().unwrap());
    
    pin.set_low().unwrap();
    assert!(pin.is_low().unwrap());
}

// 自定义测试运行器
fn custom_test_runner(tests: &[&dyn Fn() -> TestResult]) {
    println!("Starting {} tests...", tests.len());
    
    for (i, test) in tests.iter().enumerate() {
        println!("Running test #{}", i + 1);
        match test() {
            TestResult::Passed => println!("Test #{} passed", i + 1),
            TestResult::Failed(msg) => println!("Test #{} failed: {}", i + 1, msg),
        }
    }
    
    println!("All tests completed");
}

#[entry]
fn main() -> ! {
    // 使用默认测试运行器
    // test_runner();
    
    // 或者使用自定义测试运行器
    custom_test_runner(&[&test_math_operations, &test_hal_operations]);
    
    loop {}
}

// 模拟GPIO实现
struct MockGpio {
    state: bool,
}

impl MockGpio {
    fn new() -> Self {
        Self { state: false }
    }
    
    fn set_high(&mut self) -> Result<(), &'static str> {
        self.state = true;
        Ok(())
    }
    
    fn set_low(&mut self) -> Result<(), &'static str> {
        self.state = false;
        Ok(())
    }
    
    fn is_high(&self) -> Result<bool, &'static str> {
        Ok(self.state)
    }
    
    fn is_low(&self) -> Result<bool, &'static str> {
        Ok(!self.state)
    }
}

注意事项

  1. 在嵌入式环境中,确保测试不会影响实际的硬件状态
  2. 考虑内存限制,避免在测试中使用过多内存
  3. 对于时间敏感的测试,可能需要调整容忍范围
  4. 某些测试可能需要特定的硬件设置才能运行

embedded-test为嵌入式Rust开发提供了简单有效的测试解决方案,特别适合在持续集成环境中验证嵌入式系统的功能。

回到顶部