如何为PostgreSQL扩展构建完整测试体系?pg_pathman三层测试方案详解
【免费下载链接】pg_pathmanPartitioning tool for PostgreSQL项目地址: https://gitcode.com/gh_mirrors/pg/pg_pathman
pg_pathman 是一个经典的 PostgreSQL 分区工具扩展,它不仅实现了 RANGE / HASH 分区、分区自动创建等核心功能,更值得学习的是它构建的一套完整测试体系:回归测试、cmocka 单元测试与 Python 集成测试三层防线,配合 Docker 一键执行和代码覆盖率统计,堪称 PostgreSQL 扩展测试的"教科书级"范例。本文带你从零看懂这套体系是怎么搭起来的。
🧩 全景图:pg_pathman 的四层测试防线
在动手之前,先看清整个测试架构。pg_pathman 的测试由四类组成:
| 测试类型 | 位置 | 作用 | 是否需要数据库 |
|---|---|---|---|
| SQL 回归测试 | sql/+expected/ | 验证 SQL 语句的实际输出 | 需要 |
| 隔离(并发)测试 | specs/ | 验证多会话并发行为 | 需要 |
| cmocka 单元测试 | tests/cmocka/ | 纯 C 层面测试内部算法 | 不需要 |
| Python 集成测试 | tests/python/ | 验证回归测试覆盖不到的场景 | 需要 |
入口脚本run_tests.sh按顺序驱动以上全部流程,最后生成覆盖率报告。这种"分层 + 单入口"的设计,是新手搭建测试体系最值得抄的作业。
🗂️ 第一层:回归测试——用 SQL 对答案
回归测试是 PostgreSQL 扩展的"标配",pg_pathman 在 Makefile 中通过REGRESS变量注册了 30 多个测试用例,如pathman_basic、pathman_inserts、pathman_hashjoin等。
它的工作方式非常直观:
sql/目录下每个.sql文件是一段"操作脚本",比如 sql/pathman_basic.sql 会创建分区表并执行各种查询;- 测试框架把每条语句在真实数据库里跑一遍;
- 把实际输出与
expected/目录下同名的期望文件逐行对比,不一致即失败。
这种"录制-回放-对答案"的模式有三个优点:
- 门槛低:会写 SQL 就能写测试;
- 可读性强:期望文件本身就是一份行为文档;
- 防回归:任何一次修改导致输出变化都会立刻暴露。
细节加分项:防止冗余期望文件
pg_pathman 还配了一个小技巧脚本 expected/test_variants.sh:PostgreSQL 支持为同一测试生成带数字后缀的多个输出文件(如xxx_1.out、xxx_2.out),该脚本会自动diff这些变体,如果发现内容完全相同就报警告,防止测试文件越积越多、互相冗余。这是很多项目容易忽略的"测试卫生"细节。
⚔️ 第二层:隔离测试——把并发场景"钉"死
并发行为是分区场景的重灾区:两个事务同时插入数据、一个提交一个回滚,后台工作进程该建几个分区?这类问题用回归测试根本测不出来。
pg_pathman 使用 PostgreSQL 自带的隔离测试框架,在 specs/ 目录下放置测试脚本,例如 specs/insert_nodes.spec 定义了s1、s2两个会话,每个会话拆成多个 step(开始事务、插入、回滚、查看分区),再用permutation枚举出各种执行顺序:
- s1 回滚、s2 提交 → 验证只建出 s2 数据所需的分区;
- 两个都回滚 → 验证分区不落地。
给新手的建议:凡是涉及"多事务 + 自动创建分区"之类的逻辑,都建议用隔离测试把关键排列组合固定下来,而不是靠人工手动试。
🧪 第三层:cmocka 单元测试——不启动数据库也能测
纯 C 写的内部算法(比如把一堆区间合并去重的 rangeset 数据结构)如果必须启动完整数据库才能测,效率极低。pg_pathman 的解法在 tests/cmocka/ 目录:
- rangeset_tests.c 是真正的测试用例,基于 cmocka 框架编写;
missing_list.c、missing_bitmapset.c等文件补齐了无法直接链接进测试程序的 PostgreSQL 基础数据结构实现;- 编译时直接链接扩展源码里的
src/rangeset.o,测试二进制rangeset_tests独立运行,完全不依赖数据库。
这个目录的 Makefile 展示了关键技巧:通过pg_config --includedir-server获取头文件路径,再链接-lcmocka。新手做 C 扩展单元测试时,"把被测对象和最小依赖编译成独立程序"这个思路完全可以照搬。
🐍 第四层:Python 集成测试——补齐回归测试的盲区
有些特性靠静态 SQL 对比根本验证不了,例如:分区创建后台任务(BGW)在并发压力下是否会重复建分区、大量并发插入时的边界行为、FDW 外部分区的联动等。这些场景由 tests/python/partitioning_test.py 承担。
它基于testgres库(一个能动态创建、配置、销毁 PostgreSQL 测试实例的工具),核心套路:
- 用
get_new_node()拉起临时数据库节点; - 多线程 / 多进程并发执行 SQL,模拟真实压力;
- 用断言检查最终状态(分区数量、数据完整性、JSON 输出等)。
运行方式很简单,先安装依赖再跑单测即可:
pip3 install testgres python3 -m unittest partitioning_test如果只想测某个特定编译版本的 PostgreSQL,设置PG_CONFIG环境变量指向对应的pg_config即可;不想测 FDW 部分时设置FDW_DISABLED=1跳过。详细说明见 tests/python/README.md。
🔁 隐藏彩蛋:升级一致性检查器
分区工具经常要处理"旧版本升级"问题。pg_pathman 在 tests/update/ 目录提供了一组工具:
dump_pathman_objects.sql:把扩展创建的所有对象 dump 成 SQL;check_update.py:自动执行ALTER EXTENSION pg_pathman UPDATE,并对比"升级后的数据库"与"全新安装"的 dump 结果是否一致;- 还有
pgbench压力脚本(tests/python/pgbench_scripts/)用于辅助验证。
这解决了一个很实际的问题:升级路径不会悄悄改变数据库结构,对用户来说是无感知的。
🐳 一键执行:Docker + 四级测试强度
整套测试最优雅的地方在于一键运行。docker-compose.yml 只有一行服务定义,配合 Dockerfile.tmpl 模板,可以在任意 PostgreSQL 版本的基础镜像上搭建完整测试环境(含 cmocka、valgrind、clang 静态分析等依赖)。
入口脚本run_tests.sh根据LEVEL环境变量提供四档强度,新手可以按需选择:
| 强度 | 包含内容 | 适用场景 |
|---|---|---|
standard | 编译 + 回归测试 + Python 测试 + cmocka 测试 | 日常开发 |
scan-build | 增加 clang 静态分析(scan-build) | 提交前体检 |
hardcore | 重新编译带 cassert 断言的 PostgreSQL 内核 | 深度验证 |
nightmare | 用 valgrind 内存检查跑整个数据库 | 发布前终极考验 |
其中hardcore/nightmare档位会下载对应版本的 PostgreSQL 源码,对 14 及以上版本还会应用 patches/ 目录下的核心补丁(如REL_14_STABLE-pg_pathman-core.diff),重新编译出一个"带调试开关"的数据库实例再跑测试——这就是跨版本兼容性的底气来源。
覆盖率:让"测试是否充分"可量化
注意run_tests.sh中编译时始终带上了-coverage(gcov)参数,测试跑完后执行gcov生成覆盖率文件,再上传到覆盖率平台。这样每个 PR 都能直观看到"这次改动覆盖了几个百分点的代码",而不是凭感觉说"我测过了"。
💡 给新手的 4 点借鉴
- 分层,别贪多:SQL 能测的别上 Python,纯函数逻辑别启动数据库,每层解决自己的问题;
- 统一入口:一个
run_tests.sh+ 一个 Docker 文件,让"跑测试"的成本降到一条命令; - 测试也要做卫生:像
test_variants.sh那样定期检查测试资产是否冗余; - 用数据说话:接入 gcov 覆盖率统计,让测试充分程度可度量、可追溯。
pg_pathman 的这套体系(回归 + 隔离 + 单元测试 + 集成 + 升级校验 + 覆盖率)体量适中、职责清晰,非常适合作为你自己 PostgreSQL 扩展项目的测试模板。
【免费下载链接】pg_pathmanPartitioning tool for PostgreSQL项目地址: https://gitcode.com/gh_mirrors/pg/pg_pathman
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考