2、测试环境搭建:交叉编译工具链、模拟器与开发板、测试框架选择
好,咱们进入第二章。测试环境搭建,说白了就是给你的模型找个「家」。这个家安不好,后面所有测试都是空中楼阁。我见过太多团队,模型在 PC 上跑得飞起,一上板子就崩,最后发现是工具链版本不对。嗯,咱们今天就把这事彻底捋清楚。
2.1 交叉编译工具链:打通 PC 与嵌入式设备的桥梁
嵌入式设备资源有限,你不可能直接在板子上写代码、编译。所以咱们得在强大的 PC 上编译,生成目标设备能运行的二进制文件。这就是交叉编译。
工具链的核心三件套:
- 编译器:比如 arm-linux-gnueabihf-gcc。名字里就藏着目标架构、操作系统、ABI 等信息。
- 链接器:负责把目标文件链接成可执行文件。
- 库文件:目标设备上的 C/C++ 运行时库,比如 libc、libstdc++。
我个人习惯,拿到一块新开发板,第一件事就是去芯片厂商官网下载官方提供的工具链。为什么?因为官方版本经过充分验证,踩坑最少。我曾经图省事,用了一个社区版的工具链,结果浮点运算结果总差一点点,排查了整整两天,最后换回官方版就解决了。
避坑指南:
我曾经在项目里遇到过一个诡异问题:模型推理结果在板子上和 PC 上不一致。后来发现是工具链的浮点 ABI 设置不对。ARM 有 hard-float 和 soft-float 之分,选错了,浮点参数传递方式就变了,结果自然对不上。
工具链安装示例(以 ARM Cortex-A 系列为例):
# 下载官方工具链
wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf.tar.xz
# 解压到指定目录
tar -xf gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf.tar.xz -C /opt/
# 设置环境变量
export PATH=/opt/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin:$PATH
# 验证安装
arm-none-linux-gnueabihf-gcc --version
你想想看,工具链装好后,怎么验证它能不能用?我一般会写一个最简单的 Hello World,然后静态编译,放到板子上跑。如果这一步通了,后面就顺了。
2.2 模拟器与开发板:测试的两条腿
开发板贵,而且数量有限。模拟器就是你的「虚拟开发板」,可以在 PC 上模拟目标设备的 CPU、内存、外设。但模拟器终究是模拟的,性能、时序、外设行为都和真实硬件有差距。
什么时候用模拟器?
- 早期功能验证:比如模型推理逻辑对不对。
- 单元测试:跑 Google Test 用例,验证算法模块。
- 回归测试:每次代码提交后,快速跑一遍。
什么时候必须上开发板?
- 性能测试:推理延迟、内存带宽、功耗。
- 外设交互:摄像头、显示屏、传感器。
- 长时间稳定性测试:跑 72 小时,看会不会死机。
我的经验:
我建议团队里至少配 3 块开发板。一块给开发人员日常调试,一块给测试人员跑自动化测试,一块作为备用。别问我为什么知道——有一次主力板子烧了,项目直接停了两天。
常用模拟器对比:
| 模拟器 | 适用架构 | 特点 | 启动速度 |
|---|---|---|---|
| QEMU | ARM, RISC-V, x86 等 | 开源,支持全系统模拟 | 中等 |
| Renode | ARM, RISC-V | 支持多核,调试能力强 | 较快 |
| Zephyr 模拟器 | ARM, x86 | 专为 Zephyr RTOS 优化 | 快 |
说白了,模拟器帮你快速迭代,开发板帮你做最终验证。两条腿走路,才稳当。
2.3 测试框架选择:Google Test vs Catch2
测试框架,就是帮你写测试用例、组织测试、生成报告的工具。嵌入式 C/C++ 项目里,最常用的就是 Google Test 和 Catch2。
Google Test(gtest):
- Google 出品,文档齐全,社区活跃。
- 支持参数化测试、死亡测试、值参数化。
- 集成到 CMake 非常方便。
Catch2:
- 单头文件库,部署简单。
- 使用 BDD 风格,测试用例可读性强。
- 支持 Section,可以嵌套测试场景。
我个人的选择标准:
- 如果团队里有人用过 gtest,那就用 gtest。学习成本最低。
- 如果项目是全新的,我倾向于 Catch2。因为它部署简单,而且 BDD 风格写出来的测试用例,产品经理都能看懂。
- 如果项目对二进制大小有严格限制,Catch2 的单头文件方式更友好。
注意:
在嵌入式设备上跑单元测试,内存是个大问题。我曾经在只有 64KB RAM 的 MCU 上跑 gtest,结果堆栈溢出。后来改用 Catch2,并且把测试用例拆成多个小文件,才搞定。所以,选框架之前,先看看你的目标设备有多少内存。
Google Test 示例(交叉编译):
// test_model.cpp
#include <gtest/gtest.h>
#include "model_runner.h"
TEST(ModelTest, InferenceResult) {
ModelRunner runner;
float input[10] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};
float output[5];
runner.load_model("model.tflite");
runner.run_inference(input, output);
// 验证输出是否在预期范围内
for (int i = 0; i < 5; i++) {
EXPECT_NEAR(output[i], expected_output[i], 1e-5);
}
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Catch2 示例(交叉编译):
// test_model.cpp
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#include "model_runner.h"
TEST_CASE("Model inference produces correct results", "[model]") {
ModelRunner runner;
float input[10] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};
float output[5];
runner.load_model("model.tflite");
runner.run_inference(input, output);
SECTION("Output values are within tolerance") {
for (int i = 0; i < 5; i++) {
REQUIRE(output[i] == Approx(expected_output[i]).epsilon(1e-5));
}
}
}
你可能会问,这两个框架在嵌入式上怎么选?其实核心就三点:内存占用、编译速度、社区支持。我个人建议,如果项目规模大、团队人多,选 gtest;如果项目小、追求快速上手,选 Catch2。
总结一下:
测试环境搭建,不是装个工具链就完事了。你得想清楚:
- 工具链版本和芯片架构是否匹配?
- 模拟器和开发板怎么分工?
- 测试框架能不能在目标设备上跑起来?
这三个问题想明白了,你的测试环境就稳了。后面写测试用例、跑自动化,都会顺风顺水。
好,这一章就到这里。下一章咱们聊聊「测试用例设计:从单元测试到集成测试」,到时候我会分享一些我在实际项目中踩过的坑,保证让你少走弯路。