4. 创建 Mock 表结构:使用临时表模拟外部依赖表、定义表结构与约束
在 Mock 依赖隔离场景中,核心目标是将被测单元与真实的外部表、视图或远端表解耦。通过创建临时表或会话级表结构来模拟外部依赖,可以确保测试在完全受控的环境下运行,不受真实数据变更、权限或网络延迟的影响。
4.1 为什么选择临时表作为 Mock 载体
临时表(TEMPORARY TABLE)在 PostgreSQL 中具备以下天然优势,使其成为 Mock 外部依赖的理想选择:
| 特性 | 临时表行为 | Mock 场景价值 |
|---|---|---|
| 会话隔离 | 数据仅对创建会话可见,连接断开后自动清理 | 测试之间互不干扰,无需手动清理 Mock 数据 |
| DDL 灵活 | 支持索引、约束、默认值、注释等完整 DDL | 可精确模拟真实表的列类型、非空约束、主键等结构 |
| 名称覆盖 | 临时表可隐藏同名的永久表(schema 优先级高于 public) | 无需修改业务 SQL 即可重定向查询到 Mock 数据 |
| 零持久化 | 不写入 WAL(特定场景)或仅会话级持久 | 避免测试数据污染生产或开发数据库 |
实践建议: 在单元测试或集成测试的 setup 阶段,优先使用
CREATE TEMPORARY TABLE ... (LIKE ... INCLUDING ALL) 快速克隆真实表结构,再按需调整约束或数据。
4.2 定义 Mock 表结构:核心步骤
创建 Mock 表结构通常遵循以下流程,确保与真实依赖表的接口兼容:
- 分析外部依赖表结构:获取真实表的列名、数据类型、长度、非空约束、主键、唯一约束、外键引用(若需要模拟)。
- 创建临时表:使用
CREATE TEMPORARY TABLE定义与真实表相同的列结构,但通常只保留对测试有影响的约束。 - 定义约束与索引:按需添加主键、唯一约束、检查约束、索引,确保 Mock 表的行为与真实表在查询计划层面一致。
- 插入 Mock 数据:在测试用例中插入预设的返回数据集或边界值。
- 验证结构一致性:通过
pg_attribute或information_schema对比 Mock 表与真实表的列差异。
关键原则: Mock 表的结构应遵循“最小必要”原则——只定义被测 SQL 实际引用的列和约束,避免过度模拟增加维护成本。
4.3 典型 Mock 表定义示例
假设业务系统依赖一张名为 orders 的外部表(来自远端数据库或另一个 schema),其真实结构如下:
-- 真实表结构(仅作参考,不执行)
CREATE TABLE public.orders (
order_id BIGSERIAL PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date DATE NOT NULL DEFAULT CURRENT_DATE,
total_amount NUMERIC(12,2) NOT NULL CHECK (total_amount >= 0),
status VARCHAR(20) NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
在测试会话中,我们创建对应的 Mock 临时表:
-- 创建 Mock 临时表,仅包含测试所需的列和约束
CREATE TEMPORARY TABLE mock_orders (
order_id BIGSERIAL PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date DATE NOT NULL,
total_amount NUMERIC(12,2) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending'
);
-- 添加检查约束(模拟真实表的 CHECK)
ALTER TABLE mock_orders ADD CONSTRAINT mock_orders_amount_check
CHECK (total_amount >= 0);
-- 添加索引(若测试涉及 order_date 范围查询)
CREATE INDEX idx_mock_orders_date ON mock_orders(order_date);
-- 注释说明 Mock 用途
COMMENT ON TABLE mock_orders IS 'Mock 表:模拟 public.orders 用于隔离测试';
注意: 临时表默认创建在
pg_temp schema 中。如果业务 SQL 使用了 schema 限定名(如 public.orders),临时表无法直接覆盖。此时需要使用 SET search_path TO pg_temp, public; 调整会话的 schema 搜索路径。
4.4 使用 LIKE 快速克隆结构
当真实表结构复杂且列较多时,手动定义 Mock 表容易遗漏或写错类型。PostgreSQL 提供了 LIKE 子句,可快速复制表结构:
-- 快速克隆结构(不包含数据)
CREATE TEMPORARY TABLE mock_orders (LIKE public.orders INCLUDING ALL)
ON COMMIT DROP; -- 事务结束后自动删除
-- 移除不需要的约束(如外键引用真实表)
ALTER TABLE mock_orders DROP CONSTRAINT IF EXISTS orders_customer_id_fkey;
-- 清空自增序列(可选,从 1 开始)
ALTER SEQUENCE mock_orders_order_id_seq RESTART WITH 1;
LIKE 选项 |
作用 | Mock 场景推荐 |
|---|---|---|
INCLUDING DEFAULTS |
复制列的默认值表达式 | 推荐,保持默认行为一致 |
INCLUDING CONSTRAINTS |
复制 CHECK、NOT NULL、UNIQUE、PRIMARY KEY | 推荐,但需注意外键可能引用真实表 |
INCLUDING INDEXES |
复制索引(包括主键索引) | 按需,若测试涉及索引扫描则推荐 |
INCLUDING STORAGE |
复制列的存储参数 | 通常不需要 |
INCLUDING COMMENTS |
复制表和列的注释 | 可选,便于文档追溯 |
4.5 Mock 表与真实表的约束差异处理
在隔离测试中,Mock 表不需要完全复制真实表的所有约束,尤其是以下类型需要特别处理:
- 外键约束:通常移除或替换为指向 Mock 表自身的外键,避免依赖其他真实表。
- 唯一约束:若测试不涉及唯一性冲突检测,可移除以减少数据插入限制。
- 检查约束:保留与业务逻辑相关的检查(如金额非负),移除与测试无关的复杂表达式。
- 排他约束:通常移除,因为 Mock 场景很少需要范围排他。
调试技巧: 使用
\d+ mock_orders 在 psql 中查看 Mock 表的完整结构,与 \d+ public.orders 对比,确保关键列和约束一致。
4.6 结构对比与验证
为确保 Mock 表与真实表在接口层面兼容,可以执行以下查询进行自动化验证:
-- 对比列名、数据类型、非空约束
SELECT
a.attname AS column_name,
a.attnotnull AS not_null,
format_type(a.atttypid, a.atttypmod) AS data_type
FROM pg_attribute a
JOIN pg_class c ON a.attrelid = c.oid
WHERE c.relname = 'mock_orders'
AND a.attnum > 0
AND NOT a.attisdropped
ORDER BY a.attnum;
将输出结果与真实表的 pg_attribute 查询结果做差集,即可发现缺失或类型不匹配的列。
4.7 Mock 表结构设计模式总结
下表总结了不同依赖类型对应的 Mock 表结构策略:
| 外部依赖类型 | Mock 表结构策略 | 约束处理要点 |
|---|---|---|
| 同库不同 schema 的表 | 临时表 + LIKE 克隆,调整 search_path |
移除外键,保留主键和检查约束 |
| 远端外部表(FDW) | 临时表 + 手动定义相同列结构 | 无需索引,仅保留 NOT NULL 和默认值 |
| 视图(VIEW) | 临时表 + 定义视图返回的列集合 | 通常无约束,仅需匹配列名和类型 |
| 分区表 | 临时表 + 单表结构,不创建分区 | 移除分区键约束,保留分区键列定义 |
4.8 常见陷阱与规避
- 序列冲突:临时表使用
BIGSERIAL时,序列起始值可能受全局序列影响。建议在插入前执行ALTER SEQUENCE ... RESTART重置。 - Schema 优先级误解:临时表仅在
pg_temp在search_path中排在目标 schema 之前时才能覆盖同名表。务必检查当前会话的search_path。 - 事务内可见性:
ON COMMIT DROP的临时表在事务提交后自动消失,适合单事务测试;跨事务测试应使用ON COMMIT PRESERVE ROWS或默认行为。 - 权限模拟:临时表不继承真实表的权限设置。若测试涉及行级安全(RLS),需在 Mock 表上显式启用
ALTER TABLE ... ENABLE ROW LEVEL SECURITY。
最佳实践: 将 Mock 表创建逻辑封装在测试框架的 setup 函数中,使用
SAVEPOINT 和 ROLLBACK TO SAVEPOINT 实现细粒度数据隔离,避免每次测试都重建表结构。