如何回滚连续API调用产生的数据库操作以助力自动化测试?
自动化测试中跨API数据库操作的回滚方案(C#/TypeScript)
后端(C#)实现方案
1. 事务ID绑定会话方案
这是最贴合需求的方案,核心思路是用唯一事务ID把多个独立API的数据库操作绑定到同一个事务中:
- 新增三个测试专用接口:
- 创建事务接口:生成唯一
transactionId,开启数据库事务并将事务对象与ID绑定存储(比如用ConcurrentDictionary<string, IDbContextTransaction>做内存缓存),返回ID给前端。 - 提交事务接口:根据传入的
transactionId找到对应事务,执行提交并清理缓存。 - 回滚事务接口:根据传入的
transactionId找到对应事务,执行回滚并清理缓存。
- 创建事务接口:生成唯一
- 改造现有Insert/Update/Delete API,添加
transactionId参数,接口内部通过ID获取共享事务,用该事务执行数据库操作。
示例代码(EF Core):
// 全局缓存存储事务,仅测试环境使用 private static readonly ConcurrentDictionary<string, IDbContextTransaction> _transactionCache = new(); // 创建事务接口 [HttpPost("test/create-transaction")] public IActionResult CreateTestTransaction() { var transaction = _dbContext.Database.BeginTransaction(); var transactionId = Guid.NewGuid().ToString(); _transactionCache.TryAdd(transactionId, transaction); // 设置超时自动清理,避免连接泄漏 _ = Task.Delay(TimeSpan.FromMinutes(10)).ContinueWith(_ => { if (_transactionCache.TryRemove(transactionId, out var tx)) { tx.Rollback(); tx.Dispose(); } }); return Ok(new { TransactionId = transactionId }); } // 改造后的Insert API [HttpPost("data/insert")] public async Task<IActionResult> InsertData([FromBody] DataDto dto, [FromQuery] string? transactionId) { IDbContextTransaction? transaction = null; var useSharedTransaction = !string.IsNullOrEmpty(transactionId) && _transactionCache.TryGetValue(transactionId, out transaction); try { _dbContext.Data.Add(new DataEntity { Name = dto.Name, Value = dto.Value }); if (useSharedTransaction) { await _dbContext.SaveChangesAsync(); } else { // 非测试场景仍用独立事务 await using var localTx = await _dbContext.Database.BeginTransactionAsync(); await _dbContext.SaveChangesAsync(); await localTx.CommitAsync(); } return Ok(); } catch { transaction?.Rollback(); return StatusCode(500); } } // 提交事务接口 [HttpPost("test/commit-transaction")] public IActionResult CommitTransaction([FromQuery] string transactionId) { if (_transactionCache.TryRemove(transactionId, out var transaction)) { try { transaction.Commit(); transaction.Dispose(); return Ok(); } catch { transaction.Rollback(); transaction.Dispose(); return StatusCode(500); } } return BadRequest("无效的事务ID"); }
2. 数据库快照/恢复方案
如果不想改动现有API,可以在自动化测试流程中加入数据库快照操作:
- 测试用例执行前,对测试数据库创建快照。
- 测试结束后,不管成功或失败,都将数据库恢复到快照状态。
- 优点:无需修改API,适合快速搭建测试环境;缺点:并行测试会互相干扰,快照恢复耗时较长,仅适合单线程测试场景。
前端(TypeScript)配合实现
前端无法直接控制数据库事务,主要负责在测试脚本中串联事务流程:
import axios from 'axios'; async function executeTestCase() { let transactionId: string | undefined; try { // 1. 开启测试事务 const txResp = await axios.post('/api/test/create-transaction'); transactionId = txResp.data.transactionId; // 2. 依次调用三个操作API,携带事务ID await axios.post('/api/data/insert', { name: "test", value: 123 }, { params: { transactionId } }); await axios.post('/api/data/update', { id: 1, value: 456 }, { params: { transactionId } }); await axios.post('/api/data/delete', { id: 1 }, { params: { transactionId } }); // 3. 测试通过,提交事务 await axios.post('/api/test/commit-transaction', undefined, { params: { transactionId } }); } catch (error) { // 4. 测试失败,回滚事务 if (transactionId) { await axios.post('/api/test/rollback-transaction', undefined, { params: { transactionId } }); } throw error; // 抛出错误标记测试失败 } }
安全问题分析
- 事务ID泄露风险:使用随机GUID作为事务ID,泄露概率极低,但需确保测试专用接口仅对测试环境开放,可通过IP白名单、测试专用API密钥验证限制访问。
- 数据库连接泄漏:必须给事务设置超时自动清理逻辑,避免因测试脚本异常导致事务长期占用数据库连接。
- 生产环境误操作:测试专用接口必须与生产环境隔离,禁止部署到生产环境,或在代码中加入环境判断,仅在测试环境启用这些接口。
- 数据一致性:共享事务期间,其他操作访问该数据会出现锁等待,测试环境需确保无其他业务流量干扰。
内容的提问来源于stack exchange,提问作者ksk
相关产品推荐
相关产品推荐

