CANN Runtime 测试框架指南:基于 gtest / gmock / mockcpp 的 DT 用例开发实战
【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime
本篇指南与 Runtime DT用例开发总纲 配套,聚焦 CANN / runtime 开源仓库中 UT/ST 用例的工程化组织方式。文中将系统梳理 Runtime 仓的测试技术栈(gtest 基类、gmock 与 mockcpp 打桩、公共桩与模块内测试资产、模拟器辅助能力、CMake 构建接入、覆盖率与 Sanitizer 统一入口),并结合仓库源码与脚本给出可落地的实操指引。读完本文,你将知道新增一个测试用例时"该复用哪些现成设施、代码放哪里、如何接入构建、如何跑覆盖率与内存检查",避免重复造轮子。
一、Runtime 仓测试技术栈总览
Runtime 仓测试主要基于gtest(Google Test)搭建用例框架,并在此基础上组合使用以下能力:
| 能力 | 定位 | 典型用途 |
|---|---|---|
testing::Test基类 | 用例生命周期管理 | 在SetUp/TearDown中完成公共初始化与清理 |
| gmock | 已有 mock 类的期望校验 | EXPECT_CALL(...)校验调用次数与参数 |
| mockcpp | 全局函数/静态函数/成员函数替换 | 打桩dlopen、driver、runtime 等系统或内部接口 |
公共桩(tests/depends/) | 跨模块复用依赖替身 | ge、mmpa、profiling、runtime、slog、tdt、toolchain 桩 |
模块内stub/、data/、json/ | 模块私有测试资产 | 仅服务单一模块的桩与场景数据 |
| 模拟器与数据管理 helper | ST/UT 场景辅助 | SimulatorMgr()创建/销毁平台模拟器、DataMgr()管理场景数据 |
tests/build_ut.sh | 统一构建/执行/统计入口 | 覆盖率、AddressSanitizer、按 target 构建执行 |
编写新用例前,应优先对照上表检查仓库内是否已有可复用的设施。本文后续各节将逐一展开。
二、测试基类:继承testing::Test组织公共初始化
绝大多数用例直接继承testing::Test,并在SetUp/TearDown中完成公共初始化与清理。基类常见职责包括:
- 恢复默认 mock 行为(防止用例间 mock 残留互相污染);
- 创建和销毁模拟器;
- 设置和恢复全局芯片类型、device、环境变量;
- 校验并清理 mock 对象(如
Mock::VerifyAndClear(...)/GlobalMockObject::verify())。
如果多个用例共享同一套初始化逻辑,应抽出统一 fixture,避免重复代码。仓库中也有通过testing::Environment做进程级全局初始化的实践,例如 tests/ut/runtime/runtime/test/main.cc 定义了RtEnvironment:
class RtEnvironment : public testing::Environment { public: virtual void SetUp() { MOCKER(drvMemAddressTranslate).stubs().will(returnValue(DRV_ERROR_INVALID_VALUE)); rtSetDevice(0); GlobalMockObject::verify(); std::cout << "Runtime UT Environment SetUP" << std::endl; } virtual void TearDown() { ut::ResetPrimaryDeviceIfActive(); std::cout << "Runtime UT Environment TearDown" << std::endl; } };该示例展示了基类职责的几个典型动作:先用 mockcpp 恢复/设置默认桩行为,再做设备初始化,随后立即GlobalMockObject::verify()校验,最后在TearDown中复位全局 device 状态。这种"设置 → 使用 → 校验 → 复位"的闭环,正是编写 fixture 时推荐的模式。
三、mock 框架:gmock 与 mockcpp 的选型与用法
3.1 gmock:面向已有 mock 类的期望校验
gmock 适用于已有 mock 类或接口的期望校验场景,常见用法包括:
EXPECT_CALL(...):声明对某个 mock 方法的调用期望(次数、参数、返回值行为);Mock::VerifyAndClear(...):校验期望是否全部满足,并清除已设置的行为。
典型场景包括 ACL 相关接口测试,以及需要验证某个 mock 对象被调用次数和参数的场景。gmock 擅长"对象方法级"的交互验证,适合被测对象依赖注入 mock 实例的架构。
3.2 mockcpp:全局函数、静态函数与成员函数替换
mockcpp 适用于全局函数、静态函数、成员函数替换场景,在 Runtime 仓内使用非常普遍。常见接口包括:
MOCKER(func):替换普通全局/静态函数;MOCKER_CPP(...):替换 C++ 成员函数/重载函数;MOCKER_CPP_VIRTUAL(...):替换虚成员函数;GlobalMockObject::verify():统一校验所有全局 mock 桩是否按预期被调用。
典型场景:
- 打桩
dlopen/dlsym/access等系统接口,隔离对真实文件系统和动态链接的依赖; - 打桩 driver、runtime、device 相关函数,将上层逻辑与硬件驱动解耦;
- 替换单例内部调用链,隔离被测类对全局状态的依赖。
MOCKER(...)的典型写法为"打桩目标 +.stubs()+.will(...)"三段式,可配合returnValue(...)、invoke(...)等行为设置。在 tests/ut/runtime/runtime/test/main.cc 中即可看到MOCKER(drvMemAddressTranslate).stubs().will(returnValue(DRV_ERROR_INVALID_VALUE))的直接使用。
3.3 选型建议
| 场景 | 推荐框架 |
|---|---|
| 类方法调用次数与参数校验 | gmock(EXPECT_CALL) |
打桩系统接口(dlopen/dlsym/access) | mockcpp(MOCKER) |
| 打桩 driver/runtime/device 函数 | mockcpp(MOCKER) |
| 替换单例内部调用链 | mockcpp(MOCKER_CPP/MOCKER_CPP_VIRTUAL) |
| 全局桩统一校验 | GlobalMockObject::verify() |
四、公共桩与测试资产的组织策略
4.1tests/depends/:跨模块复用的公共桩
tests/depends 提供跨模块复用的公共桩,新增测试前应优先检查这里是否已有可复用能力。当前主要包含:
| 目录 | 桩内容 | 对应源码 |
|---|---|---|
tests/depends/ge/ | 依赖组件(GE)相关桩 | ge_stub.cpp |
tests/depends/mmpa/ | 平台抽象层桩 | mmpa_stub.cpp |
tests/depends/profiling/ | profiling 相关桩 | profiling_stub.cpp |
tests/depends/runtime/ | runtime/drv 接口桩 | runtime_stub.cpp |
tests/depends/slog/ | 日志桩(含日志捕获能力) | slog_stub.cpp |
tests/depends/tdt/ | 数据传输相关桩 | tdt_stub.cpp |
tests/depends/toolchain/ | 工具链相关桩 | toolchain_stub.cpp |
公共桩目录下还有 acl_stub.h 以及各子目录的CMakeLists.txt,用于在构建时按需链接对应桩。在新增测试前,先搜索这里是否已有目标接口的桩实现,能显著减少重复劳动。
4.2 模块内stub/、data/、json/:模块私有测试资产
很多模块会在自己的测试目录下维护专用测试资产,例如:
- tests/ut/msprof/st/stub/:msprof ST 的私有桩,包含
drv_stub.cpp、mmpa_stub.cpp、slog_stub.cc、tsd_stub.cpp、devdrv_runtime_api_stub.cpp等与 profiling 场景强耦合的替身; tests/ut/msprof/st/data/:msprof 的场景数据(其中 data_manager.h 定义了数据管理辅助类);- tests/ut/acl/json/:ACL 测试的 JSON 配置资产,如
dumpConfig.json、profConfig.json、testDump1.json、testExceptionDump_norm.json等; tests/ut/runtime/runtime_c/stub/:Runtime C 接口测试的私有桩。
组织原则:如果某个 stub 或数据只服务于一个模块,应优先放在模块内,避免污染公共目录;只有多个模块需要复用的桩才应提升到tests/depends/。
五、模拟器与辅助能力:优先复用现有 helper
部分 ST/UT 会使用模块内现成的模拟器和数据管理能力,例如:
SimulatorMgr():创建/销毁平台模拟器,用于隔离不同 SoC/芯片平台场景;DataMgr():初始化和校验场景数据。
这类能力通常已经和模块场景耦合。例如在 msprof 的 ST 测试中,data_manager.h 定义了如下便捷入口:
inline Cann::Dvvp::Test::DataManager& DataMgr() { return Cann::Dvvp::Test::DataManager::GetInstance(); }也就是说,DataMgr()实际是Cann::Dvvp::Test::DataManager单例的别名封装,通过单例方式统一管理场景数据。新增用例时应优先复用这类现成 helper,不要重复实现一套新的模拟逻辑,否则既增加维护成本,也容易与既有场景行为不一致。
六、构建接入:将新用例接入 CMake 与build_ut.sh
6.1 CMakeLists.txt 接入点
新增测试代码后,通常需要修改对应模块的CMakeLists.txt。常见接入方式包括:
- 将新文件加入
UT_FILES:在模块CMakeLists.txt中维护的源文件列表里追加新用例文件; - 将新目录加入
add_subdirectory(...):如果新增了独立测试子目录,需要在父级CMakeLists.txt注册; - 为新可执行目标补充依赖:为新测试目标补齐所需的 stub、头文件包含路径和链接库。
仓库中 tests/ut/runtime/runtime/CMakeLists.txt 即以add_executable(runtime_utest ...)的方式组织多个测试可执行目标。以UT_FILES方式接入的示例写法如下:
set(UT_FILES "acl_queue_unittest.cpp" "acl_new_feature_unittest.cpp" )6.2 更新tests/build_ut.sh的 target 映射
如果新增了可通过bash tests/build_ut.sh -u <target>直接执行的模块,还需要同步更新 tests/build_ut.sh 中的ut_path_map和ut_name_map两个映射表:
ut_path_map:target 名 → 构建输出中的测试可执行文件所在目录(用于构建后自动查找并逐个执行);ut_name_map:target 名 → CMake 可执行目标名(用于指定cmake --build --target)。
当前脚本已注册的 target 包括full、acl、runtime、runtime_c、platform、qs/queue_schedule、aicpusd/aicpu_sched、slog、atrace、msprof、adump、tsd、error_manager、mmpa,见 tests/build_ut.sh。
6.3 常用执行命令
# 构建并执行全部测试 bash tests/build_ut.sh -u # 构建并执行指定模块 bash tests/build_ut.sh -u runtime bash tests/build_ut.sh -u acl bash tests/build_ut.sh -u msprof # 使能覆盖率(等价于 -c / --cov) bash tests/build_ut.sh -c # 使能 AddressSanitizer bash tests/build_ut.sh --asan -u runtime脚本还支持以下实用参数(见 tests/build_ut.sh):
-j<N>:构建线程数,默认 8;-v / --verbose:显示详细构建命令;--ut_timeout=<SECONDS>:为每个 UT 可执行文件设置执行超时(默认 0 表示不限制;设置为正数时依赖系统timeout命令,超时退出码 124/137 会被判定为用例超时失败);-t / --target <target>:仅构建并运行指定的 CMake 目标;--cann_3rd_lib_path=<PATH>:指定 CANN 第三方依赖包安装路径,默认./output/third_party。
说明:脚本基于 CMake 构建,构建参数(
-DENABLE_UT、-DENABLE_ASAN、-DENABLE_COV等)在 tests/build_ut.sh 的build_rts()中统一拼装;用例执行阶段会在output/report/ut下输出 gtest XML 报告,并以可执行文件为单位逐个运行、遇失败即退出。
七、覆盖率与 Sanitizer:尽早暴露问题
仓内覆盖率与内存检查的统一入口为tests/build_ut.sh:
# 覆盖率 bash tests/build_ut.sh -c # AddressSanitizer bash tests/build_ut.sh --asan -u runtime- 覆盖率:
-c / --cov会在ENABLE_COV开启的情况下,用lcov收集并过滤统计信息(过滤/usr/*、output/*、tests/*、第三方依赖与build/*等路径),最终通过genhtml在cov/目录生成 HTML 覆盖率报告,详见 tests/build_ut.sh; - AddressSanitizer:
--asan会先校验libasan.so、libubsan.so、libtsan.so是否存在(缺失时会给出apt install libasan6 libubsan1 libtsan2之类的安装提示,并要求库版本与 gcc 版本匹配,例如 gcc 9.5.0 对应libasan6),再通过LD_PRELOAD注入 sanitizer 库后运行用例,见 tests/build_ut.sh。
实践建议:如果新增测试会创建大量临时文件或目录,建议在本地同时跑一遍覆盖率或 sanitizer,尽早暴露内存泄漏、未释放资源和残留状态问题——这类问题只有在全局校验与 sanitizer 组合下才容易被稳定发现。
八、总结与上手清单
Runtime 仓的测试设施已经相当完备,新增用例时建议按以下清单操作:
- 复用优先:先检查 tests/depends 公共桩与目标模块内的
stub/、data/、json/目录,确认是否有可直接复用的桩、数据和 helper; - 选对 mock 框架:类方法期望校验用 gmock,系统接口与全局函数打桩用 mockcpp,并遵循
MOCKER(...).stubs().will(...)与GlobalMockObject::verify()的闭环; - 组织好 fixture:用
testing::Test/testing::Environment收敛公共初始化,负责 mock 恢复、模拟器创建销毁、全局状态设置与复位; - 接入构建:更新模块
CMakeLists.txt(UT_FILES/add_subdirectory/ 目标依赖),如需独立 target 则同步更新 tests/build_ut.sh 的ut_path_map与ut_name_map; - 回归验证:用
bash tests/build_ut.sh -u <target>验证用例通过,再结合-c(覆盖率)与--asan(Sanitizer)做一次完整体检。
通过这套流程,新用例可以最大程度复用既有测试设施,保持仓库测试资产的整洁与一致。更完整的用例设计规范(单场景校验、命名规范、全局状态恢复等)可进一步阅读 UT用例开发指导 与 Runtime DT用例开发总纲。
【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考