3. 基础Mock概念:Stub与Mock的区别、PostgreSQL中的函数与视图模拟
3.1 为什么需要Mock?——依赖隔离的起点
在数据库测试与开发中,一个函数或视图往往依赖多个底层对象(表、外部API、其他函数)。直接测试真实依赖会导致:
- 测试脆弱:底层表结构变更或数据清理导致测试失败。
- 环境耦合:依赖外部系统(如支付网关、第三方数据源)时无法离线测试。
- 执行缓慢:真实I/O操作拖慢测试套件。
Mock技术的核心目标就是用轻量替身替换真实依赖,使被测单元在隔离环境中稳定运行。
关键认知:Mock不是“造假”,而是“可控的模拟”。在PostgreSQL中,我们通过替换函数体、创建临时视图或使用事务级回滚来实现隔离。
3.2 Stub与Mock的核心区别
虽然两者都是测试替身,但意图与验证方式截然不同。下表从五个维度进行对比:
| 维度 | Stub(桩) | Mock(模拟对象) |
|---|---|---|
| 核心目的 | 提供预设的返回值,使被测代码能继续执行 | 验证被测代码是否按预期与依赖交互 |
| 验证方式 | 不验证调用过程,只关注输出 | 验证调用次数、参数、顺序 |
| 状态 vs 行为 | 状态验证:检查最终结果 | 行为验证:检查交互过程 |
| 典型场景 | 模拟数据库查询返回固定行 | 验证日志记录函数是否被调用一次 |
| PostgreSQL实现 | 创建返回常量的函数或视图 | 使用plpgsql钩子或临时表记录调用痕迹 |
实战口诀:
Stub —— “你给我什么,我就返回什么,不关心你怎么调用我。”
Mock —— “我盯着你,看你有没有按约定调用我。”
Stub —— “你给我什么,我就返回什么,不关心你怎么调用我。”
Mock —— “我盯着你,看你有没有按约定调用我。”
3.3 PostgreSQL中的函数模拟(Stub风格)
在PostgreSQL中,最常见的Stub实现方式是临时替换函数定义。利用事务级临时函数,可以在测试会话中覆盖真实逻辑。
3.3.1 基本语法:创建临时Stub函数
-- 假设真实函数:计算订单折扣
CREATE OR REPLACE FUNCTION get_discount(order_id INT)
RETURNS NUMERIC AS $$
-- 真实逻辑:查询价格表、促销规则等
SELECT discount FROM order_discounts WHERE oid = order_id;
$$ LANGUAGE sql;
-- 测试时创建Stub:始终返回固定折扣0.15
CREATE OR REPLACE FUNCTION get_discount(order_id INT)
RETURNS NUMERIC AS $$
SELECT 0.15::NUMERIC;
$$ LANGUAGE sql IMMUTABLE;
3.3.2 事务级隔离技巧
使用BEGIN ... ROLLBACK包裹测试,确保Stub函数不会污染其他会话:
BEGIN;
-- 在当前事务中替换函数
CREATE OR REPLACE FUNCTION get_discount(order_id INT)
RETURNS NUMERIC AS $$
SELECT 0.15::NUMERIC;
$$ LANGUAGE sql;
-- 执行被测逻辑
SELECT calculate_total(1001); -- 内部调用get_discount
-- 验证结果
-- ... 断言语句 ...
ROLLBACK; -- 函数自动恢复为原始定义
注意:
CREATE OR REPLACE FUNCTION在事务中执行后,其他并发事务不可见。这是PostgreSQL实现Mock隔离的天然优势——无需全局修改。
3.4 PostgreSQL中的视图模拟(Mock风格)
视图模拟常用于验证调用行为。例如,我们想知道某个业务函数是否正确地查询了特定视图,以及查询了哪些列。
3.4.1 创建Mock视图记录调用
-- 1. 创建日志表,记录视图被访问的痕迹
CREATE TEMP TABLE view_access_log (
called_at TIMESTAMP DEFAULT now(),
query_text TEXT
);
-- 2. 创建Mock视图,替代真实视图
CREATE OR REPLACE TEMP VIEW user_active_orders AS
WITH log AS (
INSERT INTO view_access_log (query_text)
VALUES ('user_active_orders 被查询')
RETURNING 1
)
SELECT order_id, total_amount, status
FROM (VALUES
(101, 250.00, 'shipped'),
(102, 180.50, 'pending')
) AS t(order_id, total_amount, status)
WHERE EXISTS (SELECT 1 FROM log);
3.4.2 验证调用行为
-- 执行被测函数
SELECT process_user_orders(42);
-- 验证视图是否被调用
SELECT COUNT(*) AS call_count
FROM view_access_log;
-- 期望结果:1
这种模式已经具备Mock的行为验证特征:我们不仅提供了预设数据,还记录了“是否被调用”这一行为事实。
3.5 Stub vs Mock 在PostgreSQL中的选择矩阵
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 函数返回固定值(如税率、汇率) | Stub:临时函数 | 只需返回值,不关心调用细节 |
| 视图提供测试数据集 | Stub:临时视图 + VALUES | 快速构造可控数据源 |
| 验证日志记录是否触发 | Mock:带日志钩子的视图/函数 | 需要记录调用痕迹 |
| 验证参数传递是否正确 | Mock:参数捕获函数 | 需要检查传入的具体值 |
| 性能测试中屏蔽慢查询 | Stub:返回常量的函数 | 消除I/O延迟,聚焦逻辑性能 |
3.6 内联SVG:Stub与Mock在PostgreSQL中的工作流对比
下图展示了Stub和Mock在PostgreSQL测试中的不同数据流向与验证焦点:
3.7 实战案例:从Stub到Mock的演进
假设我们需要测试一个订单汇总函数,它依赖一个外部汇率接口(通过函数get_exchange_rate(currency)模拟)。
阶段一:Stub实现(仅关注输出)
-- 真实函数(连接外部API)
CREATE OR REPLACE FUNCTION get_exchange_rate(currency TEXT)
RETURNS NUMERIC AS $$
-- 假设调用外部HTTP服务
SELECT rate FROM external_rates WHERE cur = currency;
$$ LANGUAGE sql;
-- Stub:固定返回汇率
CREATE OR REPLACE FUNCTION get_exchange_rate(currency TEXT)
RETURNS NUMERIC AS $$
SELECT CASE currency
WHEN 'USD' THEN 7.12
WHEN 'EUR' THEN 7.85
ELSE 1.00
END;
$$ LANGUAGE sql IMMUTABLE;
-- 测试:汇总订单时使用固定汇率
SELECT sum_orders_in_cny('2024-01-01', '2024-01-31');
-- 验证:结果是否等于 (订单金额 * 固定汇率) 之和
阶段二:Mock实现(验证调用行为)
-- 创建调用日志表
CREATE TEMP TABLE rate_call_log (
currency TEXT,
called_at TIMESTAMP DEFAULT now()
);
-- Mock函数:记录每次调用
CREATE OR REPLACE FUNCTION get_exchange_rate(currency TEXT)
RETURNS NUMERIC AS $$
DECLARE
v_rate NUMERIC;
BEGIN
INSERT INTO rate_call_log (currency) VALUES (currency);
-- 返回预设值
v_rate := CASE currency
WHEN 'USD' THEN 7.12
WHEN 'EUR' THEN 7.85
ELSE 1.00
END;
RETURN v_rate;
END;
$$ LANGUAGE plpgsql;
-- 测试后验证行为
SELECT sum_orders_in_cny('2024-01-01', '2024-01-31');
-- 验证:汇率函数是否被调用了预期次数?
SELECT currency, COUNT(*) AS call_count
FROM rate_call_log
GROUP BY currency;
-- 期望:USD 调用 5 次,EUR 调用 2 次
演进要点:从Stub到Mock,我们只增加了调用日志记录和行为断言。函数体本身仍然返回预设值,但验证维度从“结果正确”扩展到了“过程正确”。
3.8 本章小结
- Stub 适合“给数据、看结果”的场景,实现简单,使用
CREATE OR REPLACE FUNCTION返回常量即可。 - Mock 适合“验证交互过程”的场景,需要额外记录调用痕迹,通常结合临时表或plpgsql日志实现。
- PostgreSQL的事务隔离特性使得函数和视图的临时替换天然安全,不会影响其他会话。
- 实际项目中,80%的场景用Stub即可满足,只有需要验证调用协议时才升级为Mock。