Files
oak-editor/docs/zh/plans/gtest-migration-guide.md
T
Mike-Solar c486c853ff docs: C ABI migration campaign plans, handoffs and roadmap
Complete documentation set for the facade migration (B1-R6) and beyond:
facade-migration-roadmap, handoffs v3-v6, R5 guides, R6 cleanup plan,
R7 pure-ABI plan, long-term plans (riir/ai-agent/gtest/ui-redesign),
updated Chinese README draft, UI design mockups, and the Google Test +
struct-typedef rules in CONTRIBUTING.
2026-07-26 22:42:44 +08:00

155 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 测试统一到 Google Test — 迁移指引
> 本文指导把仓库里并存的三套测试框架统一收敛到 **Google Test**。
> 面向执行者(DeepSeek Flash 或任何接手代理),自包含,可直接照做。
> 工作分支:`c-abi-migration`。**启动前提:R5C ABI app 侧迁移)验收完
> 成之后**(R5 期间测试是唯一的回归防线,不在迁移途中换测试框架);
> 启动后可与 UI 改版计划并行(文件域不相交)。每迁完一个测试二进制
> 立即提交,每步全量 ctest 绿才进下一步。
>
> **ctest 的定位**:统一后 ctest 仍然是唯一的测试**运行入口**
> `ctest --output-on-failure`),Google Test 是唯一的测试**编写框架**。
> 两者不冲突——用 `gtest_discover_tests()` 让 ctest 按用例粒度发现 gtest 用例。
---
## 1. 现状:三套框架并存
| 框架 | 位置 | 编写方式 | 构建/注册 |
|---|---|---|---|
| **Google Test(目标形态)** | `tests/gtest/*.cpp` | `TEST()/TEST_F()/TEST_P()`,单一 `olive-gtest` 二进制 | `tests/gtest/CMakeLists.txt`,共享 `main.cpp`QApplication + offscreen + OCIO |
| 自研 OAK 宏框架 | `tests/timeline/timeline-tests.cpp``tests/compositing/compositing-tests.cpp` | `OAK_ADD_TEST(name)` + `OAK_ASSERT(x)` | `tests/CMakeLists.txt``olive_add_test()` 宏,正则扫宏生成 `main()` |
| 纯 C assert | `engine/tests/oakengine_*_test.cpp``core/tests/oakcore_*_test.cpp` | 手写 `main()` + `assert()` | `engine/CMakeLists.txt``make_oakengine_test()`,每个文件一个独立 ctest 二进制 |
## 2. 为什么统一到 Google Test
1. **断言可读性**`assert(x)` 失败只说行号;`EXPECT_EQ(a, b)` 打印左右值,
定位快一个数量级。这正是近几轮 facade 调试里最痛的一点。
2. **fixture 替代手工样板**:纯 C 测试里每个文件都手写 `oakengine_init` /
`setenv("XDG_*")` / 临时目录 / `oakengine_project_free`gfixture 的
`SetUp()/TearDown()`/`SetUpTestSuite()` 一次性收口。
3. **`GTEST_SKIP()`**:GPU/缺资源用例优雅跳过(offscreen OpenGL 不可绘现在
靠崩/超时区分,不好维护)。
4. **过滤与重复**`--gtest_filter`、重复运行(压 flaky)、死亡测试。
5. **`assert()` 在 NDEBUG 下被吞**:纯 C 测试一旦开 Release 编译就形同虚设,
这是统一的硬理由之一。
## 3. 目标结构
```
tests/gtest/ # app 集成测试(已是 gtest,保持不变,按需并入新用例)
engine/tests/ # liboakengine facade 测试,改写为 gtest
CMakeLists.txt # 一个 oakengine_gtest 目标 + gtest_discover_tests
core/tests/ # liboakcore 测试,改写为 gtest
CMakeLists.txt # 一个 oakcore_gtest 目标 + gtest_discover_tests
tests/timeline/ # 删除 olive_add_test 产物,timeline-tests.cpp 改写为 gtest
tests/compositing/ # 同上
```
- `tests/CMakeLists.txt``olive_add_test()` 宏与 `tests/testutil.h`
`OAK_ADD_TEST`/`OAK_ASSERT`/`OAK_TEST_END` 宏全部删除。
- `engine/CMakeLists.txt``make_oakengine_test()` 宏删除。
- 每个新 gtest 二进制经 `gtest_discover_tests(<target>)` 进 ctest**ctest 总
用例数不得少于迁移前**(迁移前列一张基线清单核对)。
## 4. 转换配方
### 4.1 OAK_ADD_TEST 宏框架(tests/timeline、tests/compositing
| 旧 | 新 |
|---|---|
| `OAK_ADD_TEST(name)` | `TEST(SuiteName, name)` |
| `OAK_ASSERT(x)` | `ASSERT_TRUE(x)` |
| `OAK_ASSERT_EQUAL(a, b)` | `ASSERT_EQ(a, b)`(自定义宏会打印左右值,直接换掉) |
| `TIMELINE_TEST_START`ColorManager::set_up_default_config + Project + Sequence | `class TimelineTest : public ::testing::Test { void SetUp() override {...} }` |
| `OAK_TEST_END / return OLIVE_TEST_SUCCESS` | 删除(gtest 自动判过) |
例:
```cpp
// 旧
OAK_ADD_TEST(add_track) {
TIMELINE_TEST_START;
OAK_ASSERT(sequence.track_list(Track::k_video)->get_track_count() == 1);
}
// 新
TEST_F(TimelineTest, AddTrack) {
ASSERT_EQ(sequence.track_list(Track::k_video)->get_track_count(), 1);
}
```
### 4.2 纯 C assertengine/tests、core/tests
| 旧 | 新 |
|---|---|
| 手写 `int main()` | 删除,链接共享 gtest main |
| `assert(x)` | `ASSERT_TRUE(x)` / `EXPECT_TRUE(x)` |
| `assert(fabs(a-b) < eps)` | `EXPECT_NEAR(a, b, eps)` |
| `assert(strcmp(a, b) == 0)` | `EXPECT_STREQ(a, b)` |
| `make_tmpdir()` + `setenv("XDG_*")` | `SetUpTestSuite()` 里建一次临时目录 |
| 每文件自带 `oakengine_init/shutdown` | 共享 fixture 做(见 §5.2 |
**纯 C ABI 测试照写 C 调用**gtest 文件是 C++,直接 `#include "oakengine/xxx.h"`
`oakengine_*` 函数即可,不需要把被测 API 改成 C++。断言里出现
`OakEngineNode*` 等不透明句柄比较用 `EXPECT_EQ((void*)a, (void*)b)`
**过渡期技巧(可选,不推荐长期使用)**:文件量太大时可先加一个
`#define assert(x) ASSERT_TRUE(x)` 的兼容头,把 `main()` 删掉挂进 gtest
再逐文件把 `assert` 换成语义化 `EXPECT_*`。但**最终态不许留 `assert()`**。
### 4.3 已是 Google Test 的(tests/gtest
不动。新增的 engine/core 用例如需 app 侧对象,可直接加进 `olive-gtest` 目标。
## 5. 落地步骤(按顺序,每步闭环:构建 + 全量 ctest 绿 + 提交)
### 5.1 基线
先跑 `ctest -N` 记录迁移前用例总数,存为 `docs/zh/gtest-migration-baseline.md`
(迁移后对比,总数只增不减)。
### 5.2 共享 fixture/main
- `core/tests/main.cpp``RUN_ALL_TESTS` + `SetUpTestSuite` 建 XDG 临时目录。
- `engine/tests/main.cpp`:同上,外加 `oakengine_init(OAKENGINE_INIT_HEADLESS)`
`TearDownTestSuite``oakengine_shutdown()``OAK_TEST_SOURCE_DIR`
`target_compile_definitions` 传入(照 `make_oakengine_test` 现有做法)。
- XDG 沙箱**每个二进制一份**,不要每个测试一份(与现状一致,避免并发冲突)。
### 5.3 core/tests(最小、无 Qt,先练手)
逐文件改写 `oakcore_*_test.cpp` 为 gtest,删手写 main;建 `oakcore_gtest` 目标
(链 `oakcore` + `GTest::gtest` + `GTest::gtest_main`),`gtest_discover_tests`
全量 ctest 绿后提交。
### 5.4 tests/timeline、tests/compositingOAK 宏框架)
按 §4.1 改写;建独立 gtest 目标或并入合适目标;删除 `olive_add_test` 调用、
`tests/testutil.h` 宏与 `tests/CMakeLists.txt` 中的宏定义。全量 ctest 绿后提交。
### 5.5 engine/testsfacade 测试,量最大)
按 §4.2 改写 `oakengine_*_test.cpp`;建 `oakengine_gtest` 目标(链 `oakengine`
+ Qt + gtest),删 `make_oakengine_test`。全量 ctest 绿后提交。
### 5.6 收尾
- `ctest -N` 对比基线(只增不减);全量 `--output-on-failure` 绿。
- 全仓库 grep 确认无 `OAK_ADD_TEST`/`OAK_ASSERT`/`make_oakengine_test`/
`olive_add_test` 残留。
- 更新 `docs/zh/` 相关文档与本指引标注"已完成"。
## 6. 注意事项(别踩坑)
1. **GPU/渲染用例**:沿用 `GTEST_SKIP()` 判定(参
`tests/gtest/render_worker_footage_test.cpp` 的 backend 检查与
viewer_display_repro_test 的 offscreen 跳过模式),不许靠超时/崩溃区分。
2. **offscreen/OCIO**:需要 QApplication 的用例共享 `tests/gtest/main.cpp`
环境初始化(offscreen QPA + OCIO 配置);engine/core 的无头用例走
`oakengine_init(HEADLESS)`,不要重复造 QApplication。
3. **测试数据路径**`OAK_TEST_SOURCE_DIR` 必须经 CMake 定义传入
`tests/demo.mp4` 等),不要硬编码相对路径。
4. **线程/事件**facade 事件类测试(`oakengine_events_test` 等)依赖
DirectConnection 同步语义,迁移时保持原用例的线程假设,不要引入
`QCoreApplication::processEvents` 之外的等待方式。
5. **一次性迁移 vs 渐进**:按 §5 的顺序渐进,**禁止**先删框架再慢慢补测试
(会造成不可测试的空窗)。每步都必须全量绿。
6. **ctest 仍是入口**CI/本地都继续用 `ctest --output-on-failure -j$(nproc)`
`gtest_discover_tests` 注册后,单个用例可用 `ctest -R <SuiteName.CaseName>`
`./<binary> --gtest_filter=...` 跑。