4. 自动化测试框架选型:主流框架对比与决策
做嵌入式测试这么多年,我见过太多团队在框架选型上栽跟头。有的选了重型框架,结果测试环境搭了两周还没跑通第一个用例。有的选了轻量级方案,后期维护却成了噩梦。说白了,没有最好的框架,只有最适合你项目的框架。
这一章,我就把 Robot Framework、pytest 和 Unity/CMock 这三套主流方案掰开揉碎讲清楚。最后再给一个决策矩阵,帮你快速锁定方向。
4.1 三大主流框架速览
先看一张对比表,心里有个底:
| 维度 | Robot Framework | pytest | Unity / CMock |
|---|---|---|---|
| 语言 | 关键字驱动(Python/Java 扩展) | 纯 Python | C 语言 |
| 适用层级 | 系统测试、验收测试 | 单元测试、集成测试、系统测试 | 单元测试、模块测试 |
| 学习曲线 | 中等(关键字语法需适应) | 低(Python 基础即可) | 低(C 语言原生) |
| 硬件依赖 | 弱(可纯模拟) | 中(需插件支持) | 强(直接跑在目标板) |
| 报告能力 | 强(自带 HTML/XML 报告) | 中(需插件增强) | 弱(需自建报告) |
| Mock 支持 | 需第三方库 | 内置 monkeypatch / pytest-mock | CMock 原生支持 |
4.2 Robot Framework:关键字驱动的系统测试利器
Robot Framework 最大的特点是「可读性」。测试用例写出来像自然语言,非技术人员也能看懂。我在一个车载娱乐系统项目里用过它,测试用例直接给产品经理评审,效率很高。
举个例子,一个简单的测试用例:
*** Settings ***
Library SerialLibrary
*** Test Cases ***
验证设备启动日志
[Setup] 打开串口 /dev/ttyUSB0 115200
发送命令 reboot
等待日志 5s
日志应包含 "Kernel init done"
[Teardown] 关闭串口
你看,每一步都清晰明了。但要注意,Robot Framework 的底层执行效率不高。如果测试用例超过 500 个,执行时间会明显变长。我建议把它用在「冒烟测试」和「回归测试」的场景,而不是跑全量用例。
4.3 pytest:Python 生态的瑞士军刀
pytest 是我个人最常用的框架。为什么?因为它灵活。从单元测试到系统测试,从纯软件模拟到硬件交互,它都能搞定。
看一个嵌入式场景下的 pytest 用例:
import serial
import pytest
@pytest.fixture
def serial_port():
port = serial.Serial('/dev/ttyUSB0', 115200, timeout=1)
yield port
port.close()
def test_boot_log(serial_port):
serial_port.write(b'reboot\n')
response = serial_port.read_until(b'Kernel init done', timeout=5)
assert b'Kernel init done' in response
pytest 的 fixture 机制非常强大。你可以用它管理硬件资源、模拟外部依赖、做参数化测试。我曾经在一个物联网网关项目里,用 pytest 配合 pytest-xdist 做并行测试,把 2000 个用例的执行时间从 40 分钟压缩到 8 分钟。
4.4 Unity / CMock:C 语言测试的黄金搭档
如果你的测试对象是裸机程序或 RTOS 上的 C 代码,Unity + CMock 几乎是标配。Unity 负责执行测试用例,CMock 负责生成 Mock 对象。
看一个 Unity 测试用例:
#include "unity.h"
#include "temperature_sensor.h"
void setUp(void) {}
void tearDown(void) {}
void test_temperature_reading_should_be_in_range(void) {
int16_t temp = read_temperature();
TEST_ASSERT_INT16_WITHIN(5, 25, temp);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_temperature_reading_should_be_in_range);
return UNITY_END();
}
CMock 的用法也很直接。假设你有一个 I2C 驱动,你想测试上层逻辑,但不希望真的操作硬件:
// 自动生成 Mock
#include "Mocki2c_driver.h"
void test_sensor_init_should_configure_i2c(void) {
i2c_init_Expect(0x48, 100000);
sensor_init();
}
Unity 的执行效率极高,因为它就是纯 C 代码,没有解释器开销。我在一个 STM32 项目里,直接在目标板上跑 Unity 测试,500 个用例 3 秒跑完。
4.5 框架选型决策矩阵
选型不能拍脑袋。我整理了一个决策矩阵,从 6 个维度打分(1-5 分),帮你量化判断:
| 评估维度 | Robot Framework | pytest | Unity/CMock |
|---|---|---|---|
| 硬件交互能力 | 3 | 3 | 5 |
| 用例可读性 | 5 | 4 | 3 |
| 执行效率 | 2 | 4 | 5 |
| 扩展性 | 4 | 5 | 2 |
| 社区生态 | 4 | 5 | 3 |
| 学习成本 | 3 | 5 | 4 |
怎么用这个矩阵?
- 总分 20-25:框架与项目高度匹配,可以直接采用。
- 总分 15-19:框架可用,但需要做一些定制或补充。
- 总分 10-14:框架与项目匹配度低,建议重新考虑。
举个例子,一个典型的「嵌入式单元测试」场景:
- 硬件交互能力:5(必须跑在目标板)
- 执行效率:5(用例多,要求快速反馈)
- 可读性:3(开发人员看就行)
算下来 Unity/CMock 总分 13,pytest 只有 11。所以选 Unity/CMock 更合理。
4.6 轻量级框架定制:从零搭一个够用的测试框架
有时候,现成的框架太重了。比如你的 MCU 只有 64KB Flash,跑 Unity 都嫌大。这时候就需要自己定制一个轻量级框架。
我分享一个我在 MSP430 项目里用过的方案,核心代码不到 50 行:
#include <stdio.h>
#define TEST_ASSERT(cond, msg) \
do { \
if (!(cond)) { \
printf("FAIL: %s at %s:%d\n", msg, __FILE__, __LINE__); \
failures++; \
} else { \
printf("PASS: %s\n", msg); \
passes++; \
} \
} while(0)
static int passes = 0;
static int failures = 0;
void test_adc_reading(void) {
int reading = read_adc(0);
TEST_ASSERT(reading > 0, "ADC reading should be positive");
TEST_ASSERT(reading < 4096, "ADC reading should be within 12-bit range");
}
int main(void) {
test_adc_reading();
printf("\n%d passed, %d failed\n", passes, failures);
return failures;
}
这个框架虽然简陋,但够用。它只有三个要素:
- 断言宏:检查条件,记录结果
- 计数器:统计通过/失败数量
- 报告输出:通过串口或日志输出结果
如果你需要更复杂的功能,可以逐步添加:
- 测试分组(按模块分类)
- 超时检测(防止死循环)
- 内存统计(检测堆栈溢出)
4.7 选型决策流程图
下面这张 SVG 流程图,帮你把选型思路串起来:
这张图的核心逻辑很简单:先看测试层级,再看资源限制。单元测试优先考虑 Unity/CMock,系统测试优先考虑 Robot Framework,集成测试则 pytest 更灵活。如果资源受限,不管哪个层级,都建议走轻量级定制路线。