Appearance
Codelab: Mock 硬件依赖
预计时间:20 分钟
学习目标
完成本教程后,你将能够:
- 理解 Mock 的工作原理
- 使用用户实现模式 Mock 函数
- 使用
mock()API 验证调用 - 使用声明式模式配置 Mock
场景描述
你正在开发一个嵌入式温度监控系统。系统需要:
- 读取温度传感器
- 检查温度是否超过阈值
- 触发告警
问题是:没有物理硬件,如何测试?
解决方案:使用 Mock 模拟硬件。
步骤 1: 创建项目结构
bash
mkdir -p sensor-demo/{src,include,build,tests}
cd sensor-demo步骤 2: 编写硬件抽象层
创建 include/sensor.h:
c
#ifndef SENSOR_H
#define SENSOR_H
// 硬件抽象层(HAL)- 读取传感器
int read_sensor(int sensor_id);
// 业务逻辑 - 检查温度
// 返回: 0=正常, 1=超温, -1=读取错误
int check_temperature(int sensor_id, int threshold);
// 业务逻辑 - 获取平均温度
int get_average_temperature(int sensor_id, int samples);
#endif创建 src/sensor_hw.c(硬件实现):
c
#include "sensor.h"
// 实际硬件读取(测试时会被 Mock)
int read_sensor(int sensor_id) {
// 真实硬件读取代码
// 这里返回 -1 表示没有实际硬件
return -1;
}创建 src/sensor.c(业务逻辑):
c
#include "sensor.h"
int check_temperature(int sensor_id, int threshold) {
int temp = read_sensor(sensor_id);
if (temp < 0) {
return -1; // 读取错误
}
return temp > threshold ? 1 : 0;
}
int get_average_temperature(int sensor_id, int samples) {
int sum = 0;
int valid_count = 0;
for (int i = 0; i < samples; i++) {
int temp = read_sensor(sensor_id);
if (temp >= 0) {
sum += temp;
valid_count++;
}
}
if (valid_count == 0) {
return -1; // 没有有效读数
}
return sum / valid_count;
}步骤 3: 编译源代码
bash
gcc -c src/sensor.c -o build/sensor.o -Iinclude
gcc -c src/sensor_hw.c -o build/sensor_hw.o -Iinclude步骤 4: 编写 Mock 测试(用户实现模式)
创建 tests/test_sensor.c:
c
#include "sensor.h"
#include <anvil/mock.h>
// Mock 状态变量
static int mock_temperature = 25;
// 用户实现模式 - Mock read_sensor
__attribute__((mock))
int read_sensor(int sensor_id) {
return mock_temperature;
}
// 初始化:每个测试前重置状态
__attribute__((test_initialize))
int setup(void) {
mock_temperature = 25; // 默认温度
mock(read_sensor).reset();
return 0;
}
/*
* 设计说明:测试正常温度(低于阈值)
* 预期结果:25度低于80阈值,返回0
*/
__attribute__((test_method))
int test_temp_normal(void) {
mock_temperature = 25;
int result = check_temperature(0, 80);
return result == 0;
}
/*
* 设计说明:测试高温(超过阈值)
* 预期结果:85度超过80阈值,返回1
*/
__attribute__((test_method))
int test_temp_high(void) {
mock_temperature = 85;
int result = check_temperature(0, 80);
return result == 1;
}
/*
* 设计说明:测试边界值(等于阈值)
* 预期结果:80度等于80阈值,返回0(不超过)
*/
__attribute__((test_method))
int test_temp_boundary(void) {
mock_temperature = 80;
int result = check_temperature(0, 80);
return result == 0;
}
/*
* 设计说明:验证传感器ID正确传递
* 预期结果:调用时使用传感器ID 5
*/
__attribute__((test_method))
int test_sensor_id_passed(void) {
check_temperature(5, 80);
// 验证调用参数
int passed_id = mock(read_sensor).arg0_val;
return passed_id == 5;
}
/*
* 设计说明:测试调用次数
* 预期结果:get_average_temperature 调用 read_sensor 5次
*/
__attribute__((test_method))
int test_call_count(void) {
mock_temperature = 30;
get_average_temperature(0, 5);
return mock(read_sensor).call_count == 5;
}
// includes: -I../include步骤 5: 运行测试
bash
anvil预期输出:
Discovered 5 test cases in 1 file.
test_sensor::test_temp_normal ......... PASSED
test_sensor::test_temp_high ........... PASSED
test_sensor::test_temp_boundary ....... PASSED
test_sensor::test_sensor_id_passed .... PASSED
test_sensor::test_call_count .......... PASSED
All 5 tests passed.步骤 6: 模拟错误情况
在 tests/test_sensor.c 中添加:
c
/*
* 设计说明:测试读取错误处理
* 预期结果:读取失败时返回-1
*/
__attribute__((test_method))
int test_read_error(void) {
mock_temperature = -1; // 模拟读取错误
int result = check_temperature(0, 80);
return result == -1;
}步骤 7: 使用声明式模式
创建另一个测试文件 tests/test_sensor_declarative.c:
c
#include "sensor.h"
#include <anvil/mock.h>
// 声明式模式 - 仅声明,不提供函数体
__attribute__((mock))
int read_sensor(int sensor_id);
__attribute__((test_initialize))
int setup(void) {
mock(read_sensor).reset();
return 0;
}
/*
* 设计说明:使用 set_return 测试正常温度
* 预期结果:返回设定的温度值
*/
__attribute__((test_method))
int test_set_return(void) {
mock(read_sensor).set_return(25);
int result = check_temperature(0, 80);
return result == 0;
}
/*
* 设计说明:使用 set_return 测试高温
* 预期结果:超过阈值时返回1
*/
__attribute__((test_method))
int test_set_return_high(void) {
mock(read_sensor).set_return(90);
int result = check_temperature(0, 80);
return result == 1;
}
/*
* 设计说明:使用自定义函数
* 预期结果:使用自定义逻辑返回值
*/
static int custom_sensor_read(int sensor_id) {
// 根据 sensor_id 返回不同温度
return 20 + sensor_id * 10;
}
__attribute__((test_method))
int test_set_custom(void) {
mock(read_sensor).set_custom(custom_sensor_read);
// sensor_id=0 -> 20度
// sensor_id=3 -> 50度
return read_sensor(0) == 20 && read_sensor(3) == 50;
}
// includes: -I../include步骤 8: 运行所有测试
bash
anvil现在应该看到两个测试文件的结果。
步骤 9: 验证调用历史
在 tests/test_sensor.c 中添加:
c
/*
* 设计说明:验证多次调用的参数历史
* 预期结果:每次调用使用正确的参数
*/
__attribute__((test_method))
int test_call_history(void) {
mock_temperature = 25;
// 使用不同的传感器ID调用
check_temperature(1, 80);
check_temperature(2, 80);
check_temperature(3, 80);
// 验证历史调用参数
int id1 = *(int *)mock(read_sensor).history(0, 0);
int id2 = *(int *)mock(read_sensor).history(1, 0);
int id3 = *(int *)mock(read_sensor).history(2, 0);
return id1 == 1 && id2 == 2 && id3 == 3;
}知识点总结
你已学会:
| 技能 | 说明 |
|---|---|
| Mock 原理 | 使用 --wrap 链接器选项拦截函数 |
| 用户实现模式 | 提供完整的 Mock 函数体 |
| 声明式模式 | 仅声明,使用 set_return/set_custom |
mock().reset() | 重置调用计数和参数历史 |
mock().arg0_val | 获取最后调用的参数 |
mock().call_count | 获取调用次数 |
mock().history(i, n) | 获取历史调用参数 |
完整项目结构
sensor-demo/
├── src/
│ ├── sensor.c
│ └── sensor_hw.c
├── include/
│ └── sensor.h
├── build/
│ ├── sensor.o
│ └── sensor_hw.o
└── tests/
├── test_sensor.c
└── test_sensor_declarative.c下一步
- Codelab: 参数化边界测试 - 学习自动生成测试用例