第4章 脚本基础:Renode脚本语言(Robot Framework)入门

说实话,很多做嵌入式测试的工程师一听到「脚本语言」就头大。我当年也是这样,总觉得写脚本是开发的事,测试嘛,点点按钮就行了。直到有一次,我在一个项目里需要重复做200次故障注入——手动操作到第50次时我就知道,这条路走不通了。

Renode 用的是 Robot Framework,一个关键字驱动的自动化测试框架。你不需要成为编程高手,但得理解它的核心玩法。这一章,我就带你把它摸透。

4.1 Robot Framework 到底是什么?

Robot Framework 说白了就是一个「搭积木」的工具。你把测试步骤写成一个个关键字,然后像拼乐高一样把它们组合起来。Renode 选择它,是因为它天生适合硬件仿真场景——你可以在测试用例里同时控制仿真器、检查寄存器、注入故障,还能做断言。

核心概念三件套:

  • 测试用例(Test Case):一个完整的测试场景,比如「验证UART在时钟故障下能否恢复」
  • 关键字(Keyword):可复用的操作单元,比如「打开仿真」、「写入寄存器」、「注入位翻转」
  • 变量(Variable):存储动态数据,比如设备地址、超时时间、期望值

我习惯把测试用例想象成菜谱,关键字就是「切菜」、「热油」、「翻炒」这些动作,变量则是「盐5克」、「水200毫升」这样的配料。你想想看,是不是一下子就通了?

4.2 第一个测试用例:Hello Renode

别急着搞复杂的东西。我们先写一个最简单的用例,确认环境能跑起来。

*** Settings ***
Library    Renode

*** Test Cases ***
Hello Renode Should Work
    [Documentation]    验证Renode仿真器能正常启动
    Start Emulation
    Sleep    1s
    Stop Emulation
    Log    Hello Renode 测试通过!

这段代码做了三件事:启动仿真、等1秒、关闭仿真。看起来简单吧?但我在项目里见过有人直接跳过这个步骤,结果折腾半天发现是环境没配好。嗯,这里要注意——永远先跑通最小用例。

我的习惯:每开始一个新项目的测试套件,我都会先写一个「空转用例」,只启动和停止仿真。这能快速排除环境问题,避免后面排查时怀疑人生。

4.3 关键字:你的积木块

关键字是 Robot Framework 的灵魂。Renode 提供了一组内置关键字,比如 Start Emulation、Write To Register、Inject Fault。但你也可以自己写关键字,把重复操作封装起来。

举个例子,我在测试一个 SPI 驱动时,每次都要做「配置SPI → 发送数据 → 检查响应」这三步。于是我就封装了一个关键字:

*** Keywords ***
SPI Transaction Should Succeed
    [Arguments]    ${data_to_send}    ${expected_response}
    Write To Register    0x40001000    ${data_to_send}
    ${actual}=    Read From Register    0x40001004
    Should Be Equal    ${actual}    ${expected_response}
    Log    SPI事务成功:发送 ${data_to_send},收到 ${actual}

这样一来,测试用例就变得非常干净:

*** Test Cases ***
Test SPI Normal Mode
    SPI Transaction Should Succeed    0xA5    0xA5

Test SPI With Clock Fault
    Inject Clock Glitch    frequency=1000000
    SPI Transaction Should Succeed    0xA5    0x00
    # 这里预期失败,因为时钟被破坏了

你看,测试逻辑一目了然。我曾经在团队里推广这种写法,新人上手速度至少快了一倍。

4.4 变量:别写死,灵活点

变量用 ${} 包裹。你可以定义全局变量、用例级变量,甚至从文件读取。我最常用的是 *** Variables *** 区:

*** Variables ***
${UART_BASE_ADDR}    0x40001000
${TIMEOUT_MS}        5000
${EXPECTED_BAUD}     115200

为什么要用变量?因为硬件地址、超时时间这些值,在项目迭代中经常变。如果你写死在每个用例里,改起来就是一场噩梦。我踩过这个坑——有一次芯片改版,UART 基地址变了,我花了整整一个下午改测试脚本。从那以后,所有地址我都用变量管理。

避坑指南:变量名不要用中文,虽然 Robot Framework 支持,但 Renode 的底层日志解析可能会乱码。我曾经因为这个排查了三个小时,最后发现是变量名里的「波特率」三个字导致的。

4.5 测试用例的组织结构

一个完整的测试套件通常长这样:

*** Settings ***
Library    Renode
Library    Collections

*** Variables ***
${DEVICE}    /dev/ttyUSB0
${BAUD_RATE}    115200

*** Keywords ***
Open Serial Port
    [Arguments]    ${port}    ${baud}
    Log    打开串口 ${port},波特率 ${baud}
    # 实际调用Renode关键字

*** Test Cases ***
Serial Port Open And Close
    [Setup]    Log    测试开始
    Open Serial Port    ${DEVICE}    ${BAUD_RATE}
    Close Serial Port
    [Teardown]    Log    测试结束

注意 [Setup] 和 [Teardown],这是 Robot Framework 的生命周期管理。每个用例执行前后都会自动调用,非常适合做资源初始化和清理。我建议你养成习惯——每个用例都写 Teardown,否则仿真器状态可能会污染下一个用例。

4.6 知识体系总览

下面这张图,是我自己梳理的 Robot Framework 在 Renode 测试中的知识结构。你可以把它当作本章的「地图」:

Robot Framework 在 Renode 测试中的知识体系 测试套件(.robot) 测试用例 关键字 变量 Setup/Teardown Documentation 内置关键字 自定义关键字 全局变量 用例级变量 核心原则:关键字驱动 + 变量分离 + 生命周期管理 每个测试用例 = 关键字的组合 + 变量的注入 + 清晰的Setup/Teardown

4.7 常用关键字速查表

下面这些关键字,是我在项目中用得最频繁的。建议你收藏一下:

关键字 作用 使用频率
Start Emulation 启动 Renode 仿真 每个用例必用
Stop Emulation 停止仿真,释放资源 每个用例必用
Write To Register 写入寄存器 极高
Read From Register 读取寄存器值 极高
Inject Fault 注入故障(位翻转、毛刺等) 高
Should Be Equal 断言两个值相等 高
Sleep 等待指定时间 中
Log 输出日志 调试时常用

小技巧:在 Renode 的交互式控制台里,你可以用 help 命令查看所有可用关键字。我经常一边写用例一边开着控制台,随时查关键字签名,比翻文档快多了。

4.8 一个完整的实战用例

最后,我给你看一个我在项目中真实用过的用例。它测试的是「在时钟故障下,UART 能否正确报告错误状态」:

*** Settings ***
Library    Renode
Library    Collections

*** Variables ***
${UART_STATUS_REG}    0x40002008
${CLOCK_FAULT_BIT}    0x04

*** Keywords ***
UART Should Report Clock Fault
    ${status}=    Read From Register    ${UART_STATUS_REG}
    ${fault_flag}=    Evaluate    ${status} & ${CLOCK_FAULT_BIT}
    Should Be True    ${fault_flag} == ${CLOCK_FAULT_BIT}
    Log    UART正确报告了时钟故障

*** Test Cases ***
UART Clock Fault Detection
    [Documentation]    验证UART在时钟故障时能设置状态寄存器的故障位
    Start Emulation
    
    # 注入时钟故障
    Inject Clock Glitch    target=uart    frequency=500000
    
    # 等待UART检测到故障
    Sleep    100ms
    
    # 检查状态寄存器
    UART Should Report Clock Fault
    
    # 清除故障
    Write To Register    ${UART_STATUS_REG}    0x00
    
    Stop Emulation

这个用例虽然短,但包含了完整的测试流程:启动 → 注入故障 → 等待 → 断言 → 清理。你写测试用例时,也尽量保持这个结构。我在团队里定了一条规矩:没有 Teardown 的用例不准提交。虽然严格了点,但确实省了很多排查时间。