← 中文资源

简体中文版本

隔离测试之间的数据夹具

为每个 worker 从迁移好的模板复制独立数据库,每个测试用事务回滚,彻底摆脱对执行顺序的依赖。

阅读英文原文

概述

测试换个顺序就失败,说明用例之间共享了状态,通常是数据库:一个用例留下数据,另一个假设表是空的,结果取决于调度顺序。每个用例前清空所有表虽然可行,但很慢,而且在并行 worker 下依然脆弱。正确目标是让每个测试都有确定的起点,并由测试框架保证,而不是靠约定。

步骤

  1. 建立一次模板数据库并执行迁移,模板中只有结构没有数据:
createdb app_template && DATABASE_URL=postgres://localhost/app_template pnpm migrate
  1. 每个 worker 从模板复制一个数据库:
const dbName = `app_test_${process.env.VITEST_POOL_ID ?? process.pid}`;
await admin.query(`CREATE DATABASE ${dbName} TEMPLATE app_template`);
  1. 每个测试用事务包裹,结束后回滚,不再需要 TRUNCATE:
beforeEach(async () => { client = await pool.connect(); await client.query('BEGIN'); });
afterEach(async () => { await client.query('ROLLBACK'); client.release(); });
  1. 把事务绑定的 client 注入仓储层,确保被测代码使用同一个连接;若 handler 直接从连接池取连接,回滚无法隐藏它的写入。
  2. 在全局 teardown 中删除各 worker 的数据库。

验证

开启并行运行后所有测试应通过,重复运行结果一致。故意让测试 A 插入用户、测试 B 统计用户数并断言为零,两者在任意顺序下都应通过。检查每个测试结束后表中没有新增行,且 worker 之间的数据互不可见。

注意事项

  • 事务回滚无法隔离其他连接上的写入、序列值与会话级状态。
  • 序列在回滚后仍会前进,不要断言精确 id,应使用返回的 id。
  • 模板数据库不能有活动连接,复制前需断开连接池。
  • 会话级 advisory lock 不会随回滚释放,应使用 pg_advisory_xact_lock。
  • 每个 worker 一个数据库会占用磁盘与连接数,必须清理,否则本地会逐渐耗尽资源。