桩函数与驱动函数:集成测试的两大基石
做嵌入式集成测试,你绕不开两个东西——桩函数和驱动函数。说白了,它们就是测试环境里的「替身」和「导演」。我刚开始带项目时,团队里有人觉得写桩和驱动是浪费时间,结果模块联调时问题一堆,定位花了三倍时间。嗯,从那以后,我再也不敢小看这两个基础概念了。
一句话记住:驱动函数是「调用者」,桩函数是「被调用者」。测试时,驱动主动发起调用,桩被动响应并返回预设结果。
桩函数(Stub)定义与作用
桩函数,就是替身。当被测模块需要调用某个下层函数,但那个函数还没写好、或者不方便在测试环境里跑(比如硬件相关、网络通信),我们就写一个简化版替上去。
它的核心作用有三个:
- 隔离依赖——让被测模块不依赖真实的下层实现
- 控制返回值——可以预设正常值、边界值、异常值
- 记录调用行为——检查被测模块是否真的调用了、传参对不对
我在项目中遇到过最典型的场景:一个传感器驱动还没调通,但上层算法模块要测。我就写了个桩函数,每次返回固定温度值25.0。算法模块跑通了,等传感器驱动就绪后直接替换桩,一次通过。
我的习惯:桩函数不要写太复杂。够用就行,能返回预设值、能记录调用次数和参数,就够了。写复杂了反而容易引入新bug。
驱动函数(Driver)定义与作用
驱动函数,就是测试的发起者。它负责调用被测模块的接口,传入测试数据,收集结果。你可以把它理解成一个「临时主函数」。
它的核心作用:
- 构造测试场景——初始化被测模块,准备输入数据
- 执行测试用例——按顺序调用被测接口
- 收集并判断结果——比对实际输出和预期值
你想想看,没有驱动函数,你怎么让被测模块跑起来?总不能每次都改main函数吧。驱动函数就是给被测模块搭了个「测试舞台」。
桩与驱动的调用关系
它们的关系其实很清晰。我画了一张图,你看一眼就明白了:
流程很简单:
- 驱动函数调用被测模块的接口
- 被测模块执行过程中,遇到下层依赖,调用桩函数
- 桩函数返回预设值,被测模块继续执行
- 驱动函数收集被测模块的输出,与预期值比对
我曾经踩过的坑:桩函数返回值和真实模块不一致。比如真实函数返回的是int,桩函数返回了float,类型隐式转换导致被测模块行为异常。排查了两天才发现。所以写桩时,函数签名一定要和原函数完全一致。
典型应用场景分析
光说概念太虚,我列几个实际项目中常见的场景:
| 场景 | 驱动做什么 | 桩做什么 |
|---|---|---|
| 硬件尚未就绪 | 调用传感器数据采集接口 | 返回预设的传感器值(温度、压力等) |
| 外部通信模块缺失 | 调用数据发送接口 | 模拟接收方,返回ACK或超时 |
| 异常路径覆盖 | 传入非法参数 | 返回错误码或抛出异常 |
| 时序/并发测试 | 按特定时序调用多个接口 | 模拟不同响应延迟 |
| 第三方库未集成 | 调用加密/校验接口 | 返回固定校验值,不依赖真实库 |
我印象最深的是做车载ECU测试那次。CAN通信模块的硬件还在调试,但上层应用要测。我写了三个桩函数:一个模拟发送成功、一个模拟总线繁忙、一个模拟超时。驱动函数循环调用这三种场景,把应用层的异常处理逻辑测了个遍。等硬件就绪后,桩函数直接替换成真实驱动,零改动通过。
一个小技巧:写桩函数时,可以加一个「调用计数器」和「参数记录器」。这样驱动函数在测试结束后,可以检查被测模块是否按预期调用了桩函数、传参是否正确。这个习惯帮我抓到了不少隐蔽的调用顺序bug。
桩与驱动的代码骨架
光说不练假把式。我写个最简单的例子,你感受一下结构:
/* 桩函数:模拟底层传感器读取 */
int Stub_ReadSensor(void)
{
static int callCount = 0;
callCount++;
printf("[Stub] ReadSensor 被调用,次数:%d\n", callCount);
return 25; /* 固定返回25度 */
}
/* 驱动函数:测试上层温度处理模块 */
void Driver_TestTempProcess(void)
{
int result;
int expected = 25;
/* 初始化被测模块 */
TempProcess_Init();
/* 执行测试 */
result = TempProcess_GetTemperature();
/* 判断结果 */
if (result == expected) {
printf("[Driver] 测试通过,结果=%d\n", result);
} else {
printf("[Driver] 测试失败,期望=%d,实际=%d\n", expected, result);
}
}
这个例子虽然简单,但骨架是通用的。驱动函数负责「搭台」,桩函数负责「唱戏」,被测模块在中间「演」。你只要把这个模式套到自己的项目里,集成测试就能快速跑起来。
核心要点回顾:
- 驱动函数是测试的发起者,负责调用和判断
- 桩函数是依赖的替身,负责返回预设结果
- 驱动和桩配合,让被测模块在隔离环境中被充分测试
- 写桩时注意函数签名一致,写驱动时注意场景覆盖