必须用begin/rollback控制事务边界,测试开头显式begin、结尾统一rollback;postgresql需begin...exception捕获raise异常;须覆盖触发器依赖的字段组合边界;避免拼写ddl,优先测日志是否漏发。

必须用 BEGIN / ROLLBACK 控制事务边界
触发器在事务内执行,但多数测试环境(如 pytest + psycopg2、JDBC 默认配置)会自动 commit,导致你 INSERT 后查不到拦截效果,UPDATE 后看不到 NEW 值是否被改写——根本没法验证它到底有没有运行。
所有测试用例开头必须显式 BEGIN,结尾统一 ROLLBACK,不依赖框架的 auto-commit 清理机制。
- PostgreSQL 支持
SAVEPOINT,可嵌套验证多步链式触发(比如 BEFORE UPDATE → AFTER UPDATE → INSERT 日志) - SQLite 也支持
SAVEPOINT和ROLLBACK TO - MySQL 5.7 及更早版本只能靠完整事务回滚,测链式逻辑时容易串扰;8.0+ 虽支持 SAVEPOINT,但行级触发器对批量语句(
INSERT INTO ... SELECT)仍按行触发,OLD/NEW永远是单行,别指望拿到“本次影响 100 行”的上下文
覆盖触发器真正依赖的字段组合边界
边界不是“空字符串”或“超长文本”,而是触发器逻辑中显式判断的字段值组合。例如一个 BEFORE UPDATE 触发器只在 status = 'pending' 且 amount > 100 时重设 priority,那必须测:
-
status不变('pending' → 'pending'),amount从 90 → 110:应触发 -
status变化但不满足条件('draft' → 'pending',amount = 50):不应触发 -
status从'pending' → 'done',同时amount也变:触发器是否错误读取了OLD.amount?需检查日志或目标字段 -
INSERT时某列为DEFAULT(如created_at TIMESTAMP DEFAULT NOW()),触发器里读NEW.created_at在 MySQL 中可能是NULL,在 PostgreSQL 中已填充,行为不一致
主动捕获 RAISE / SIGNAL 异常,不能让它崩掉测试
触发器里 RAISE EXCEPTION(PostgreSQL)或 SIGNAL SQLSTATE(MySQL 8.0+)会让整个事务中断,后续断言直接跳过——你得主动捕获,而不是等测试报错退出。
- PostgreSQL 测试必须包在
BEGIN ... EXCEPTION WHEN others THEN ... END块里,再用GET STACKED DIAGNOSTICS提取错误信息做断言 - MySQL 中无法用存储过程“软捕获”
SIGNAL,建议把校验类逻辑移到BEFORE触发器中用SET NEW.col = ...修正,而非硬抛错 - 日志类触发器要优先验证通知是否漏发:哪怕主表操作成功,日志表也必须有对应记录;否则上线后审计就断档
避免测试代码和触发器定义强耦合
直接在测试里拼 CREATE TRIGGER 语句,等于把 DDL 当业务逻辑写死。一旦触发器重命名、条件改写或迁移到新 schema,所有测试全挂。
- 测试前确保目标表和触发器已存在(CI 环境走迁移脚本加载,本地用固定 SQL 文件初始化)
- 用独立
schema或临时表(CREATE TEMP TABLE)隔离测试,避免和其他测试用例的触发器互相干扰 - 不要在测试里
DROP TRIGGER再重建——PostgreSQL 不支持CREATE OR REPLACE TRIGGER,强行删重建会破坏依赖顺序
最容易被忽略的是:测试里查“最终状态”不等于验证了触发器行为。比如 BEFORE UPDATE 修改了 NEW.amount,你得在 RETURN NEW 后立刻 SELECT amount FROM table WHERE id = ?,而不是等事务外查整张表——中间态才是触发器真正的输出。










