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 表结构通常遵循以下流程,确保与真实依赖表的接口兼容:

  1. 分析外部依赖表结构:获取真实表的列名、数据类型、长度、非空约束、主键、唯一约束、外键引用(若需要模拟)。
  2. 创建临时表:使用 CREATE TEMPORARY TABLE 定义与真实表相同的列结构,但通常只保留对测试有影响的约束。
  3. 定义约束与索引:按需添加主键、唯一约束、检查约束、索引,确保 Mock 表的行为与真实表在查询计划层面一致。
  4. 插入 Mock 数据:在测试用例中插入预设的返回数据集或边界值。
  5. 验证结构一致性:通过 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) 临时表 + 定义视图返回的列集合 通常无约束,仅需匹配列名和类型
分区表 临时表 + 单表结构,不创建分区 移除分区键约束,保留分区键列定义
Mock 表结构创建流程 ① 分析真实表结构 ② 创建临时表 ③ 定义约束与索引 ④ 插入 Mock 数据 ⑤ 验证结构一致性 关键原则 最小必要原则 只模拟被测 SQL 实际引用的列和约束

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 实现细粒度数据隔离,避免每次测试都重建表结构。