Files
oak-editor/docs/zh/plans/gtest-migration-guide.md
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

8.1 KiB
Raw Permalink Blame History

测试统一到 Google Test — 迁移指引

本文指导把仓库里并存的三套测试框架统一收敛到 Google Test。 面向执行者(DeepSeek Flash 或任何接手代理),自包含,可直接照做。 工作分支:c-abi-migration启动前提:R5(C 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.cppQApplication + offscreen + OCIO
自研 OAK 宏框架 tests/timeline/timeline-tests.cpptests/compositing/compositing-tests.cpp OAK_ADD_TEST(name) + OAK_ASSERT(x) tests/CMakeLists.txtolive_add_test() 宏,正则扫宏生成 main()
纯 C assert engine/tests/oakengine_*_test.cppcore/tests/oakcore_*_test.cpp 手写 main() + assert() engine/CMakeLists.txtmake_oakengine_test(),每个文件一个独立 ctest 二进制

2. 为什么统一到 Google Test

  1. 断言可读性assert(x) 失败只说行号;EXPECT_EQ(a, b) 打印左右值, 定位快一个数量级。这正是近几轮 facade 调试里最痛的一点。
  2. fixture 替代手工样板:纯 C 测试里每个文件都手写 oakengine_init / setenv("XDG_*") / 临时目录 / oakengine_project_freegfixture 的 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.txtolive_add_test() 宏与 tests/testutil.hOAK_ADD_TEST/OAK_ASSERT/OAK_TEST_END 宏全部删除。
  • engine/CMakeLists.txtmake_oakengine_test() 宏删除。
  • 每个新 gtest 二进制经 gtest_discover_tests(<target>) 进 ctestctest 总 用例数不得少于迁移前(迁移前列一张基线清单核对)。

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_STARTColorManager::set_up_default_config + Project + Sequence class TimelineTest : public ::testing::Test { void SetUp() override {...} }
OAK_TEST_END / return OLIVE_TEST_SUCCESS 删除(gtest 自动判过)

例:

// 旧
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.cppRUN_ALL_TESTS + SetUpTestSuite 建 XDG 临时目录。
  • engine/tests/main.cpp:同上,外加 oakengine_init(OAKENGINE_INIT_HEADLESS) TearDownTestSuiteoakengine_shutdown()OAK_TEST_SOURCE_DIRtarget_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=... 跑。