Zephyr Twister测试框架完全指南
引言
在嵌入式系统开发中,测试是确保代码质量和可靠性的关键环节。Zephyr RTOS提供了一套强大的测试框架------Twister,它能够自动发现、编译、运行和报告测试结果,大大简化了测试流程。
本文将详细介绍Twister测试框架的概念、功能、使用方法,帮助您建立一套完整的测试体系。
一、Twister测试框架概述
1.1 什么是Twister
Twister 是Zephyr官方提供的测试运行器(Test Runner),它是一个基于Python的自动化测试工具,负责:
#mermaid-svg-LpSBBz4xGKC3whnt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LpSBBz4xGKC3whnt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LpSBBz4xGKC3whnt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LpSBBz4xGKC3whnt .error-icon{fill:#552222;}#mermaid-svg-LpSBBz4xGKC3whnt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LpSBBz4xGKC3whnt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LpSBBz4xGKC3whnt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LpSBBz4xGKC3whnt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LpSBBz4xGKC3whnt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LpSBBz4xGKC3whnt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LpSBBz4xGKC3whnt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LpSBBz4xGKC3whnt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LpSBBz4xGKC3whnt .marker.cross{stroke:#333333;}#mermaid-svg-LpSBBz4xGKC3whnt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LpSBBz4xGKC3whnt p{margin:0;}#mermaid-svg-LpSBBz4xGKC3whnt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LpSBBz4xGKC3whnt .cluster-label text{fill:#333;}#mermaid-svg-LpSBBz4xGKC3whnt .cluster-label span{color:#333;}#mermaid-svg-LpSBBz4xGKC3whnt .cluster-label span p{background-color:transparent;}#mermaid-svg-LpSBBz4xGKC3whnt .label text,#mermaid-svg-LpSBBz4xGKC3whnt span{fill:#333;color:#333;}#mermaid-svg-LpSBBz4xGKC3whnt .node rect,#mermaid-svg-LpSBBz4xGKC3whnt .node circle,#mermaid-svg-LpSBBz4xGKC3whnt .node ellipse,#mermaid-svg-LpSBBz4xGKC3whnt .node polygon,#mermaid-svg-LpSBBz4xGKC3whnt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LpSBBz4xGKC3whnt .rough-node .label text,#mermaid-svg-LpSBBz4xGKC3whnt .node .label text,#mermaid-svg-LpSBBz4xGKC3whnt .image-shape .label,#mermaid-svg-LpSBBz4xGKC3whnt .icon-shape .label{text-anchor:middle;}#mermaid-svg-LpSBBz4xGKC3whnt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LpSBBz4xGKC3whnt .rough-node .label,#mermaid-svg-LpSBBz4xGKC3whnt .node .label,#mermaid-svg-LpSBBz4xGKC3whnt .image-shape .label,#mermaid-svg-LpSBBz4xGKC3whnt .icon-shape .label{text-align:center;}#mermaid-svg-LpSBBz4xGKC3whnt .node.clickable{cursor:pointer;}#mermaid-svg-LpSBBz4xGKC3whnt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LpSBBz4xGKC3whnt .arrowheadPath{fill:#333333;}#mermaid-svg-LpSBBz4xGKC3whnt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LpSBBz4xGKC3whnt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LpSBBz4xGKC3whnt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LpSBBz4xGKC3whnt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LpSBBz4xGKC3whnt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LpSBBz4xGKC3whnt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LpSBBz4xGKC3whnt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LpSBBz4xGKC3whnt .cluster text{fill:#333;}#mermaid-svg-LpSBBz4xGKC3whnt .cluster span{color:#333;}#mermaid-svg-LpSBBz4xGKC3whnt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LpSBBz4xGKC3whnt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LpSBBz4xGKC3whnt rect.text{fill:none;stroke-width:0;}#mermaid-svg-LpSBBz4xGKC3whnt .icon-shape,#mermaid-svg-LpSBBz4xGKC3whnt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LpSBBz4xGKC3whnt .icon-shape p,#mermaid-svg-LpSBBz4xGKC3whnt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LpSBBz4xGKC3whnt .icon-shape .label rect,#mermaid-svg-LpSBBz4xGKC3whnt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LpSBBz4xGKC3whnt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LpSBBz4xGKC3whnt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LpSBBz4xGKC3whnt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Twister
测试发现
编译测试
运行测试
结果报告
扫描仓库
解析配置
多平台编译
并行构建
QEMU运行
硬件运行
Native运行
JSON报告
Xunit报告
HTML报告
1.2 Twister的核心功能
| 功能 | 说明 |
|---|---|
| 自动测试发现 | 扫描仓库中的测试应用 |
| 多平台编译 | 为不同开发板编译测试 |
| 模拟器支持 | QEMU、native_sim等 |
| 硬件测试 | 支持连接真实硬件 |
| 并行执行 | 多任务并行编译和测试 |
| 结果报告 | 生成JSON、Xunit、HTML报告 |
| 覆盖率分析 | 代码覆盖率统计 |
| Pytest集成 | 支持Python测试框架 |
1.3 测试执行流程
报告生成 硬件设备 QEMU模拟器 CMake构建系统 Twister测试框架 用户 报告生成 硬件设备 QEMU模拟器 CMake构建系统 Twister测试框架 用户 #mermaid-svg-utxe7cyB2V9eMUYB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-utxe7cyB2V9eMUYB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-utxe7cyB2V9eMUYB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-utxe7cyB2V9eMUYB .error-icon{fill:#552222;}#mermaid-svg-utxe7cyB2V9eMUYB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-utxe7cyB2V9eMUYB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-utxe7cyB2V9eMUYB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-utxe7cyB2V9eMUYB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-utxe7cyB2V9eMUYB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-utxe7cyB2V9eMUYB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-utxe7cyB2V9eMUYB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-utxe7cyB2V9eMUYB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-utxe7cyB2V9eMUYB .marker.cross{stroke:#333333;}#mermaid-svg-utxe7cyB2V9eMUYB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-utxe7cyB2V9eMUYB p{margin:0;}#mermaid-svg-utxe7cyB2V9eMUYB .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-utxe7cyB2V9eMUYB text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-utxe7cyB2V9eMUYB .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-utxe7cyB2V9eMUYB .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-utxe7cyB2V9eMUYB .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-utxe7cyB2V9eMUYB .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-utxe7cyB2V9eMUYB #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-utxe7cyB2V9eMUYB .sequenceNumber{fill:white;}#mermaid-svg-utxe7cyB2V9eMUYB #sequencenumber{fill:#333;}#mermaid-svg-utxe7cyB2V9eMUYB #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-utxe7cyB2V9eMUYB .messageText{fill:#333;stroke:none;}#mermaid-svg-utxe7cyB2V9eMUYB .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-utxe7cyB2V9eMUYB .labelText,#mermaid-svg-utxe7cyB2V9eMUYB .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-utxe7cyB2V9eMUYB .loopText,#mermaid-svg-utxe7cyB2V9eMUYB .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-utxe7cyB2V9eMUYB .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-utxe7cyB2V9eMUYB .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-utxe7cyB2V9eMUYB .noteText,#mermaid-svg-utxe7cyB2V9eMUYB .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-utxe7cyB2V9eMUYB .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-utxe7cyB2V9eMUYB .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-utxe7cyB2V9eMUYB .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-utxe7cyB2V9eMUYB .actorPopupMenu{position:absolute;}#mermaid-svg-utxe7cyB2V9eMUYB .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-utxe7cyB2V9eMUYB .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-utxe7cyB2V9eMUYB .actor-man circle,#mermaid-svg-utxe7cyB2V9eMUYB line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-utxe7cyB2V9eMUYB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 运行twister命令 扫描测试应用 解析testcase.yaml 编译测试应用 生成可执行文件 启动模拟器运行 执行测试 返回结果 烧录并运行 执行测试 返回结果 生成测试报告 输出报告
二、测试配置文件详解
2.1 testcase.yaml配置文件
每个测试应用都需要一个 testcase.yaml 配置文件:
yaml
# testcase.yaml示例
# 通用配置
common:
# 允许的平台列表
platform_allow:
- qemu_x86
- nrf52840dk_nrf52840
- native_sim
# 默认测试平台
integration_platforms:
- qemu_x86
# 测试超时时间(秒)
timeout: 60
# 是否为慢速测试
slow: false
# 测试级别
level: integration
# 标签
tags:
- kernel
- thread
# 测试用例定义
tests:
# 测试名称(格式:模块.名称.子测试)
kernel.thread.basic:
# 测试描述
description: "Basic thread test"
# 测试标签
tags:
- thread
- basic
# 测试套件
testsuite: tests/kernel/thread
# 构建配置
extra_configs:
- CONFIG_DEBUG=y
- CONFIG_LOG=y
# 硬件要求
hardware:
min_flash: 32KB
min_ram: 8KB
# 测试执行方式
harness: console
# 控制台匹配模式
harness_config:
type: one_line
regex:
- "PASS"
2.2 配置字段说明
| 字段 | 说明 | 示例 |
|---|---|---|
platform_allow |
允许运行的平台 | qemu_x86, nrf52840dk |
platform_exclude |
排除的平台 | stm32f4_discovery |
integration_platforms |
CI默认测试平台 | qemu_x86 |
timeout |
测试超时时间(秒) | 60 |
slow |
是否慢速测试 | false |
level |
测试级别 | smoke/integration/full |
tags |
标签分类 | kernel, thread |
harness |
测试执行方式 | console, pytest, ctest |
harness_config |
执行器配置 | type, regex |
extra_configs |
额外Kconfig配置 | CONFIG_DEBUG=y |
extra_args |
额外命令行参数 | --enable-asan |
2.3 测试级别说明
| 级别 | 说明 | 执行频率 |
|---|---|---|
| smoke | 冒烟测试,快速验证核心功能 | 每次提交 |
| integration | 集成测试,验证模块间协作 | 每日构建 |
| full | 完整测试,覆盖所有场景 | 每周构建 |
| acceptance | 验收测试,用户场景验证 | 版本发布前 |
三、常用Twister命令
3.1 基本命令
bash
# 运行默认测试(扫描当前目录)
./scripts/twister
# 指定测试目录
./scripts/twister -T tests/kernel
# 指定平台
./scripts/twister -p qemu_x86
# 指定多个平台
./scripts/twister -p qemu_x86 -p nrf52840dk_nrf52840
# 列出所有可用测试
./scripts/twister --list-tests
# 列出所有可用平台
./scripts/twister --list-platforms
# 查看帮助
./scripts/twister --help
3.2 并行执行
bash
# 使用所有CPU核心
./scripts/twister -j $(nproc)
# 指定并行任务数
./scripts/twister -j 8
3.3 输出控制
bash
# 详细输出
./scripts/twister -v
# 更详细输出
./scripts/twister -vv
# 指定输出目录
./scripts/twister -O twister-out
# 生成HTML报告
./scripts/twister --html-report
# 生成XML报告
./scripts/twister --xunit-report
3.4 过滤测试
bash
# 按标签过滤
./scripts/twister -t kernel
# 排除标签
./scripts/twister -e slow
# 按名称过滤
./scripts/twister -s kernel.thread.basic
# 按级别过滤
./scripts/twister --level smoke
# 按平台模式过滤
./scripts/twister --platform-pattern "nrf52*"
# 只构建不运行
./scripts/twister -b
# 只运行已构建的测试
./scripts/twister --test-only
3.5 代码覆盖率
bash
# 启用代码覆盖率
./scripts/twister --enable-coverage
# 指定覆盖率工具
./scripts/twister --enable-coverage --coverage-tool gcovr
# 指定覆盖率格式
./scripts/twister --enable-coverage --coverage-formats html
# 指定覆盖率平台
./scripts/twister --enable-coverage --coverage-platform qemu_x86
3.6 高级选项
bash
# 运行所有测试(包括慢速测试)
./scripts/twister --all --enable-slow
# 启用地址Sanitizer
./scripts/twister --enable-asan
# 启用内存泄漏检测
./scripts/twister --enable-lsan
# 启用未定义行为检测
./scripts/twister --enable-ubsan
# 创建ROM/RAM报告
./scripts/twister --create-rom-ram-report
# 生成内存占用报告
./scripts/twister --footprint-report
# 随机打乱测试顺序
./scripts/twister --shuffle-tests
四、Ztest测试框架
4.1 Ztest概述
Ztest 是Zephyr内置的单元测试框架,与Twister配合使用。它提供了一套断言宏和测试结构:
#mermaid-svg-K1OgrvpTpSgw4FPQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K1OgrvpTpSgw4FPQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K1OgrvpTpSgw4FPQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K1OgrvpTpSgw4FPQ .error-icon{fill:#552222;}#mermaid-svg-K1OgrvpTpSgw4FPQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K1OgrvpTpSgw4FPQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K1OgrvpTpSgw4FPQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K1OgrvpTpSgw4FPQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K1OgrvpTpSgw4FPQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K1OgrvpTpSgw4FPQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K1OgrvpTpSgw4FPQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K1OgrvpTpSgw4FPQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K1OgrvpTpSgw4FPQ .marker.cross{stroke:#333333;}#mermaid-svg-K1OgrvpTpSgw4FPQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K1OgrvpTpSgw4FPQ p{margin:0;}#mermaid-svg-K1OgrvpTpSgw4FPQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-K1OgrvpTpSgw4FPQ .cluster-label text{fill:#333;}#mermaid-svg-K1OgrvpTpSgw4FPQ .cluster-label span{color:#333;}#mermaid-svg-K1OgrvpTpSgw4FPQ .cluster-label span p{background-color:transparent;}#mermaid-svg-K1OgrvpTpSgw4FPQ .label text,#mermaid-svg-K1OgrvpTpSgw4FPQ span{fill:#333;color:#333;}#mermaid-svg-K1OgrvpTpSgw4FPQ .node rect,#mermaid-svg-K1OgrvpTpSgw4FPQ .node circle,#mermaid-svg-K1OgrvpTpSgw4FPQ .node ellipse,#mermaid-svg-K1OgrvpTpSgw4FPQ .node polygon,#mermaid-svg-K1OgrvpTpSgw4FPQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-K1OgrvpTpSgw4FPQ .rough-node .label text,#mermaid-svg-K1OgrvpTpSgw4FPQ .node .label text,#mermaid-svg-K1OgrvpTpSgw4FPQ .image-shape .label,#mermaid-svg-K1OgrvpTpSgw4FPQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-K1OgrvpTpSgw4FPQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-K1OgrvpTpSgw4FPQ .rough-node .label,#mermaid-svg-K1OgrvpTpSgw4FPQ .node .label,#mermaid-svg-K1OgrvpTpSgw4FPQ .image-shape .label,#mermaid-svg-K1OgrvpTpSgw4FPQ .icon-shape .label{text-align:center;}#mermaid-svg-K1OgrvpTpSgw4FPQ .node.clickable{cursor:pointer;}#mermaid-svg-K1OgrvpTpSgw4FPQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-K1OgrvpTpSgw4FPQ .arrowheadPath{fill:#333333;}#mermaid-svg-K1OgrvpTpSgw4FPQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-K1OgrvpTpSgw4FPQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-K1OgrvpTpSgw4FPQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K1OgrvpTpSgw4FPQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-K1OgrvpTpSgw4FPQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K1OgrvpTpSgw4FPQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-K1OgrvpTpSgw4FPQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-K1OgrvpTpSgw4FPQ .cluster text{fill:#333;}#mermaid-svg-K1OgrvpTpSgw4FPQ .cluster span{color:#333;}#mermaid-svg-K1OgrvpTpSgw4FPQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-K1OgrvpTpSgw4FPQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-K1OgrvpTpSgw4FPQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-K1OgrvpTpSgw4FPQ .icon-shape,#mermaid-svg-K1OgrvpTpSgw4FPQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K1OgrvpTpSgw4FPQ .icon-shape p,#mermaid-svg-K1OgrvpTpSgw4FPQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-K1OgrvpTpSgw4FPQ .icon-shape .label rect,#mermaid-svg-K1OgrvpTpSgw4FPQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K1OgrvpTpSgw4FPQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-K1OgrvpTpSgw4FPQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-K1OgrvpTpSgw4FPQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Ztest框架
测试套件
测试用例
断言宏
Fixture
ZTEST_SUITE
setup/teardown
ZTEST
ZTEST_P
ZTEST_F
zassert_true
zassert_equal
fixture
before/after
4.2 创建测试套件
c
#include <zephyr/ztest.h>
/* 定义测试套件 */
ZTEST_SUITE(kernel_thread_suite, NULL, NULL, NULL, NULL, NULL);
/* 添加测试用例 */
ZTEST(kernel_thread_suite, test_thread_create) {
/* 测试线程创建 */
k_tid_t tid;
struct k_thread thread;
K_THREAD_STACK_DEFINE(stack, 1024);
tid = k_thread_create(&thread, stack, K_THREAD_STACK_SIZEOF(stack),
NULL, NULL, NULL, K_PRIO_PREEMPT(0), 0, K_NO_WAIT);
zassert_not_null(tid, "Thread creation failed");
zassert_true(k_thread_active(&thread), "Thread should be active");
}
ZTEST(kernel_thread_suite, test_thread_sleep) {
/* 测试线程睡眠 */
k_ticks_t start = k_ticks_get();
k_msleep(100);
k_ticks_t end = k_ticks_get();
k_ticks_t diff = end - start;
zassert_true(diff >= 100, "Sleep duration too short");
}
/* 参数化测试 */
static const struct test_param {
int input;
int expected;
} params[] = {
{1, 2},
{2, 4},
{3, 6},
};
ZTEST_P(kernel_thread_suite, test_param_example) {
const struct test_param *p = data;
zassert_equal(p->input * 2, p->expected, "Parameter test failed");
}
ZTEST_P_INSTANCE(kernel_thread_suite, test_param_example, 0);
ZTEST_P_INSTANCE(kernel_thread_suite, test_param_example, 1);
ZTEST_P_INSTANCE(kernel_thread_suite, test_param_example, 2);
4.3 断言宏
| 断言宏 | 说明 | 示例 |
|---|---|---|
zassert_true(cond, msg) |
断言条件为真 | zassert_true(ret == 0, "Failed") |
zassert_false(cond, msg) |
断言条件为假 | zassert_false(ptr == NULL, "Null") |
zassert_equal(a, b, msg) |
断言相等 | zassert_equal(a, b, "Not equal") |
zassert_not_equal(a, b, msg) |
断言不相等 | zassert_not_equal(a, b, "Equal") |
zassert_null(ptr, msg) |
断言为空 | zassert_null(ptr, "Not null") |
zassert_not_null(ptr, msg) |
断言不为空 | zassert_not_null(ptr, "Null") |
zassert_ok(ret, msg) |
断言返回OK | zassert_ok(ret, "Error") |
zassert_in_range(val, lo, hi, msg) |
断言在范围内 | zassert_in_range(x, 0, 100, "Out of range") |
4.4 测试Fixture
c
#include <zephyr/ztest.h>
/* 定义fixture结构 */
struct my_fixture {
int value;
struct k_mutex mutex;
};
/* setup函数 - 每个测试套件运行一次 */
static void *my_suite_setup(void) {
struct my_fixture *fixture = malloc(sizeof(struct my_fixture));
fixture->value = 42;
k_mutex_init(&fixture->mutex);
return fixture;
}
/* teardown函数 */
static void my_suite_teardown(void *fixture) {
free(fixture);
}
/* before函数 - 每个测试用例运行前 */
static void my_before(void *fixture) {
struct my_fixture *f = fixture;
f->value = 0;
}
/* after函数 - 每个测试用例运行后 */
static void my_after(void *fixture) {
struct my_fixture *f = fixture;
k_mutex_unlock(&f->mutex);
}
/* 使用fixture的测试套件 */
ZTEST_SUITE(my_suite, NULL, my_suite_setup, my_before, my_after, my_suite_teardown);
ZTEST_F(my_suite, test_with_fixture) {
/* 使用fixture */
fixture->value = 100;
zassert_equal(fixture->value, 100, "Fixture value wrong");
k_mutex_lock(&fixture->mutex, K_FOREVER);
/* 测试代码 */
k_mutex_unlock(&fixture->mutex);
}
五、Pytest集成
5.1 Pytest简介
Twister支持与Python的pytest框架集成,允许编写更复杂的测试逻辑:
#mermaid-svg-ZgWZn92YRUIYkbAd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZgWZn92YRUIYkbAd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZgWZn92YRUIYkbAd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZgWZn92YRUIYkbAd .error-icon{fill:#552222;}#mermaid-svg-ZgWZn92YRUIYkbAd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZgWZn92YRUIYkbAd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZgWZn92YRUIYkbAd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZgWZn92YRUIYkbAd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZgWZn92YRUIYkbAd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZgWZn92YRUIYkbAd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZgWZn92YRUIYkbAd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZgWZn92YRUIYkbAd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZgWZn92YRUIYkbAd .marker.cross{stroke:#333333;}#mermaid-svg-ZgWZn92YRUIYkbAd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZgWZn92YRUIYkbAd p{margin:0;}#mermaid-svg-ZgWZn92YRUIYkbAd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZgWZn92YRUIYkbAd .cluster-label text{fill:#333;}#mermaid-svg-ZgWZn92YRUIYkbAd .cluster-label span{color:#333;}#mermaid-svg-ZgWZn92YRUIYkbAd .cluster-label span p{background-color:transparent;}#mermaid-svg-ZgWZn92YRUIYkbAd .label text,#mermaid-svg-ZgWZn92YRUIYkbAd span{fill:#333;color:#333;}#mermaid-svg-ZgWZn92YRUIYkbAd .node rect,#mermaid-svg-ZgWZn92YRUIYkbAd .node circle,#mermaid-svg-ZgWZn92YRUIYkbAd .node ellipse,#mermaid-svg-ZgWZn92YRUIYkbAd .node polygon,#mermaid-svg-ZgWZn92YRUIYkbAd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZgWZn92YRUIYkbAd .rough-node .label text,#mermaid-svg-ZgWZn92YRUIYkbAd .node .label text,#mermaid-svg-ZgWZn92YRUIYkbAd .image-shape .label,#mermaid-svg-ZgWZn92YRUIYkbAd .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZgWZn92YRUIYkbAd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZgWZn92YRUIYkbAd .rough-node .label,#mermaid-svg-ZgWZn92YRUIYkbAd .node .label,#mermaid-svg-ZgWZn92YRUIYkbAd .image-shape .label,#mermaid-svg-ZgWZn92YRUIYkbAd .icon-shape .label{text-align:center;}#mermaid-svg-ZgWZn92YRUIYkbAd .node.clickable{cursor:pointer;}#mermaid-svg-ZgWZn92YRUIYkbAd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZgWZn92YRUIYkbAd .arrowheadPath{fill:#333333;}#mermaid-svg-ZgWZn92YRUIYkbAd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZgWZn92YRUIYkbAd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZgWZn92YRUIYkbAd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZgWZn92YRUIYkbAd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZgWZn92YRUIYkbAd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZgWZn92YRUIYkbAd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZgWZn92YRUIYkbAd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZgWZn92YRUIYkbAd .cluster text{fill:#333;}#mermaid-svg-ZgWZn92YRUIYkbAd .cluster span{color:#333;}#mermaid-svg-ZgWZn92YRUIYkbAd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZgWZn92YRUIYkbAd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZgWZn92YRUIYkbAd rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZgWZn92YRUIYkbAd .icon-shape,#mermaid-svg-ZgWZn92YRUIYkbAd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZgWZn92YRUIYkbAd .icon-shape p,#mermaid-svg-ZgWZn92YRUIYkbAd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZgWZn92YRUIYkbAd .icon-shape .label rect,#mermaid-svg-ZgWZn92YRUIYkbAd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZgWZn92YRUIYkbAd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZgWZn92YRUIYkbAd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZgWZn92YRUIYkbAd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Twister
pytest
Python测试代码
设备交互
外部工具
REST API
pytest-twister-harness
fixture支持
5.2 创建Pytest测试
目录结构:
test_foo/
├── pytest/
│ └── test_foo.py
├── src/
│ └── main.c
├── CMakeLists.txt
├── prj.conf
└── testcase.yaml
testcase.yaml配置:
yaml
tests:
sample.pytest.shell:
harness: pytest
tags:
- pytest
- shell
harness_config:
pytest_root:
- "pytest/test_shell.py"
pytest测试代码:
python
# pytest/test_shell.py
import pytest
from twister_harness import Shell
def test_shell_basic(dut):
"""测试shell基本功能"""
shell = Shell(dut)
# 发送命令
result = shell.run('version')
assert 'Zephyr' in result
# 测试多个命令
result = shell.run('kernel threads')
assert 'main' in result
# 测试错误命令
result = shell.run('invalid_command')
assert 'Unknown command' in result
5.3 Pytest Fixtures
| Fixture | 说明 |
|---|---|
dut |
Device Under Test对象 |
shell |
Shell交互对象 |
device |
设备适配器 |
5.4 运行Pytest测试
bash
# 运行pytest测试
./scripts/twister -T samples/subsys/testsuite/pytest/shell -p native_posix -v
# 指定pytest参数
./scripts/twister -T samples/subsys/testsuite/pytest/shell \
-p native_posix \
--pytest-args='-k test_shell_print_version'
六、硬件测试
6.1 硬件映射文件
yaml
# hardware_map.yaml
devices:
- id: nrf52840dk_1
type: nrf52840dk
serial: /dev/ttyACM0
runner: nrfjprog
- id: nrf52840dk_2
type: nrf52840dk
serial: /dev/ttyACM1
runner: nrfjprog
- id: qemu_x86
type: qemu_x86
runner: qemu
6.2 运行硬件测试
bash
# 指定硬件映射文件
./scripts/twister --hardware-map hardware_map.yaml
# 指定设备串口
./scripts/twister --device-serial /dev/ttyACM0
# 指定串口波特率
./scripts/twister --device-serial-baud 115200
# 启用设备测试模式
./scripts/twister --device-testing
# 指定flash命令
./scripts/twister --flash-command "nrfjprog --program"
七、测试结果分析
7.1 报告类型
| 报告类型 | 文件 | 说明 |
|---|---|---|
| JSON报告 | twister.json |
详细测试结果 |
| 测试计划 | testplan.json |
测试计划 |
| Xunit报告 | twister.xml |
标准Xunit格式 |
| HTML报告 | twister.html |
可视化报告 |
| 日志文件 | twister.log |
测试日志 |
7.2 JSON报告结构
json
{
"version": "1.0",
"testsuites": [
{
"platform": "qemu_x86",
"name": "kernel.thread.basic",
"testcases": [
{
"identifier": "test_thread_create",
"status": "passed",
"duration": 0.5,
"output": "PASS"
},
{
"identifier": "test_thread_sleep",
"status": "failed",
"duration": 0.6,
"output": "FAIL: Sleep duration too short",
"reason": "Assertion failed"
}
]
}
],
"summary": {
"total": 2,
"passed": 1,
"failed": 1,
"skipped": 0,
"errored": 0
}
}
7.3 测试状态说明
| 状态 | 说明 |
|---|---|
| passed | 测试通过 |
| failed | 测试失败 |
| skipped | 测试跳过 |
| errored | 测试错误 |
| build_failed | 构建失败 |
| timeout | 测试超时 |
| flashed | 已烧录但未运行 |
7.4 分析测试结果
bash
# 查看测试摘要
./scripts/twister -M
# 查看失败的测试
./scripts/twister -M pass
# 查看所有测试详情
cat twister-out/twister.json | python3 -m json.tool
# 统计测试结果
grep -c "\"status\": \"passed\"" twister-out/twister.json
grep -c "\"status\": \"failed\"" twister-out/twister.json
八、实际案例
8.1 创建自定义测试
步骤1:创建测试目录
bash
mkdir -p tests/my_custom_test/src
步骤2:编写测试代码
c
// tests/my_custom_test/src/main.c
#include <zephyr/ztest.h>
ZTEST_SUITE(my_custom_suite, NULL, NULL, NULL, NULL, NULL);
ZTEST(my_custom_suite, test_addition) {
zassert_equal(1 + 1, 2, "1+1 should be 2");
zassert_equal(2 + 3, 5, "2+3 should be 5");
zassert_equal(0 + 0, 0, "0+0 should be 0");
}
ZTEST(my_custom_suite, test_string) {
const char *str = "Hello Zephyr!";
zassert_true(strlen(str) > 0, "String should not be empty");
zassert_equal(strcmp(str, "Hello Zephyr!"), 0, "String mismatch");
}
void main(void) {
ztest_run_all();
}
步骤3:编写CMakeLists.txt
cmake
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(my_custom_test)
target_sources(app PRIVATE src/main.c)
步骤4:编写testcase.yaml
yaml
common:
platform_allow:
- qemu_x86
- native_sim
integration_platforms:
- qemu_x86
timeout: 30
tags:
- custom
- example
tests:
my_custom_test.basic:
description: "Custom test example"
harness: console
harness_config:
type: one_line
regex:
- "PASS"
步骤5:运行测试
bash
./scripts/twister -T tests/my_custom_test -p qemu_x86 -v
8.2 运行内核测试
bash
# 运行所有内核测试
./scripts/twister -T tests/kernel -p qemu_x86
# 运行特定测试
./scripts/twister -s kernel.thread.basic -p qemu_x86
# 运行多个测试套件
./scripts/twister -T tests/kernel/sched -T tests/kernel/thread -p qemu_x86
8.3 CI集成示例
yaml
# .github/workflows/twister.yml
name: Twister Tests
on: [push, pull_request]
jobs:
twister:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build python3-pip
pip3 install -r scripts/requirements.txt
- name: Run twister
run: |
source zephyr-env.sh
./scripts/twister -T tests/kernel -p qemu_x86 -j 4 --html-report
九、常见问题与解决方案
9.1 测试发现问题
问题1:测试未被发现
bash
# 解决:检查目录结构和配置文件
ls -la tests/my_test/
cat tests/my_test/testcase.yaml
# 确保目录包含:
# - CMakeLists.txt
# - src/main.c
# - testcase.yaml
问题2:平台不匹配
bash
# 解决:检查platform_allow配置
grep platform_allow tests/my_test/testcase.yaml
# 确保目标平台在允许列表中
9.2 编译问题
问题1:编译失败
bash
# 解决:查看编译日志
./scripts/twister -T tests/my_test -p qemu_x86 -v
# 或查看构建目录
cat twister-out/qemu_x86/tests/my_test/build.log
问题2:缺少依赖
bash
# 解决:安装必要依赖
pip3 install -r scripts/requirements.txt
9.3 运行问题
问题1:QEMU未找到
bash
# 解决:安装QEMU
sudo apt-get install qemu-system-x86
问题2:测试超时
bash
# 解决:增加超时时间
./scripts/twister -T tests/my_test -p qemu_x86 --timeout-multiplier 2
问题3:硬件连接问题
bash
# 解决:检查串口
ls /dev/ttyACM*
# 指定正确的串口
./scripts/twister --device-serial /dev/ttyACM0
9.4 报告问题
问题1:报告文件未生成
bash
# 解决:检查输出目录
ls twister-out/
# 指定输出目录
./scripts/twister -O my_output_dir
问题2:HTML报告为空
bash
# 解决:确保有测试运行
./scripts/twister --html-report -v
十、最佳实践
10.1 测试设计原则
#mermaid-svg-AQfNzZP2h1BiucNQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AQfNzZP2h1BiucNQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AQfNzZP2h1BiucNQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AQfNzZP2h1BiucNQ .error-icon{fill:#552222;}#mermaid-svg-AQfNzZP2h1BiucNQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AQfNzZP2h1BiucNQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AQfNzZP2h1BiucNQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AQfNzZP2h1BiucNQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AQfNzZP2h1BiucNQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AQfNzZP2h1BiucNQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AQfNzZP2h1BiucNQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AQfNzZP2h1BiucNQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AQfNzZP2h1BiucNQ .marker.cross{stroke:#333333;}#mermaid-svg-AQfNzZP2h1BiucNQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AQfNzZP2h1BiucNQ p{margin:0;}#mermaid-svg-AQfNzZP2h1BiucNQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AQfNzZP2h1BiucNQ .cluster-label text{fill:#333;}#mermaid-svg-AQfNzZP2h1BiucNQ .cluster-label span{color:#333;}#mermaid-svg-AQfNzZP2h1BiucNQ .cluster-label span p{background-color:transparent;}#mermaid-svg-AQfNzZP2h1BiucNQ .label text,#mermaid-svg-AQfNzZP2h1BiucNQ span{fill:#333;color:#333;}#mermaid-svg-AQfNzZP2h1BiucNQ .node rect,#mermaid-svg-AQfNzZP2h1BiucNQ .node circle,#mermaid-svg-AQfNzZP2h1BiucNQ .node ellipse,#mermaid-svg-AQfNzZP2h1BiucNQ .node polygon,#mermaid-svg-AQfNzZP2h1BiucNQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AQfNzZP2h1BiucNQ .rough-node .label text,#mermaid-svg-AQfNzZP2h1BiucNQ .node .label text,#mermaid-svg-AQfNzZP2h1BiucNQ .image-shape .label,#mermaid-svg-AQfNzZP2h1BiucNQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-AQfNzZP2h1BiucNQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AQfNzZP2h1BiucNQ .rough-node .label,#mermaid-svg-AQfNzZP2h1BiucNQ .node .label,#mermaid-svg-AQfNzZP2h1BiucNQ .image-shape .label,#mermaid-svg-AQfNzZP2h1BiucNQ .icon-shape .label{text-align:center;}#mermaid-svg-AQfNzZP2h1BiucNQ .node.clickable{cursor:pointer;}#mermaid-svg-AQfNzZP2h1BiucNQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AQfNzZP2h1BiucNQ .arrowheadPath{fill:#333333;}#mermaid-svg-AQfNzZP2h1BiucNQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AQfNzZP2h1BiucNQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AQfNzZP2h1BiucNQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AQfNzZP2h1BiucNQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AQfNzZP2h1BiucNQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AQfNzZP2h1BiucNQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AQfNzZP2h1BiucNQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AQfNzZP2h1BiucNQ .cluster text{fill:#333;}#mermaid-svg-AQfNzZP2h1BiucNQ .cluster span{color:#333;}#mermaid-svg-AQfNzZP2h1BiucNQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AQfNzZP2h1BiucNQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AQfNzZP2h1BiucNQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-AQfNzZP2h1BiucNQ .icon-shape,#mermaid-svg-AQfNzZP2h1BiucNQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AQfNzZP2h1BiucNQ .icon-shape p,#mermaid-svg-AQfNzZP2h1BiucNQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AQfNzZP2h1BiucNQ .icon-shape .label rect,#mermaid-svg-AQfNzZP2h1BiucNQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AQfNzZP2h1BiucNQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AQfNzZP2h1BiucNQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AQfNzZP2h1BiucNQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 测试设计
单一职责
可重复
隔离性
快速执行
一个测试一个断言
相同输入相同输出
测试间无依赖
避免长时间操作
10.2 目录结构规范
tests/
├── kernel/
│ ├── thread/
│ │ ├── src/
│ │ │ └── main.c
│ │ ├── CMakeLists.txt
│ │ ├── prj.conf
│ │ └── testcase.yaml
│ └── sched/
│ └── ...
├── drivers/
│ └── gpio/
│ └── ...
└── subsys/
└── bluetooth/
└── ...
10.3 测试命名规范
bash
# 测试名称格式:模块.功能.子功能
# 示例:
kernel.thread.basic
drivers.gpio.pin_config
subsys.bluetooth.adv
10.4 CI集成建议
| 场景 | 建议 |
|---|---|
| 代码提交 | 运行smoke级别测试 |
| PR合并 | 运行integration级别测试 |
| 每日构建 | 运行full级别测试 |
| 发布版本 | 运行所有测试 + 硬件测试 |
10.5 性能优化
bash
# 使用ccache加速编译
export CCACHE_DIR=~/.ccache
./scripts/twister --enable-ccache
# 并行执行
./scripts/twister -j $(nproc)
# 只运行必要测试
./scripts/twister --level smoke
# 跳过慢速测试
./scripts/twister -e slow
结束语
通过本文的详细介绍,相信您已经全面掌握了Zephyr Twister测试框架的使用方法:
| 知识点 | 内容 |
|---|---|
| Twister | 测试运行器,负责发现、编译、运行、报告 |
| testcase.yaml | 测试配置文件,定义平台、超时、标签等 |
| Ztest | 单元测试框架,提供断言宏和测试结构 |
| Pytest | 集成Python测试框架,支持复杂测试逻辑 |
| 硬件测试 | 支持连接真实硬件设备 |
| 报告 | 生成JSON、Xunit、HTML等格式报告 |
使用Twister测试框架可以帮助您:
- 确保代码质量:自动化测试捕获回归问题
- 提高开发效率:快速验证代码修改
- 降低发布风险:全面测试覆盖
- 支持持续集成:无缝集成CI流程
建议在实际项目中:
- 从简单的单元测试开始
- 逐步增加集成测试
- 利用Pytest编写复杂测试
- 在CI中运行完整测试套件
参考资料: