Django TestCase原子事务失败时数据库未回滚问题求助
原子事务单元测试异常问题排查
问题背景
要实现账户间转账的原子事务单元测试,通过transaction.atomic()保证操作要么全部成功要么全部失败。预期验证:当接收方deposit函数失败时,发送方的扣款操作会回滚,余额保持不变。但测试用例失败,显示发送方余额已减少;尝试替换TransactionTestCase无效,仅VS Code逐行调试时测试能通过。
原代码片段
import pytest from cards.models import Card, Account from unittest.mock import patch def perform_atomic(txn, sender_account, receiver_account, amount): try: with transaction.atomic(): sender_account.balance -= 1000 sender_account.save() receiver_account.deposit(amount) except Exception as error: txn.status = Transaction.TRANSACTION_STATUS_FAILED txn.save() logger.error(f"Failed Action. Reason[{error}]", exc_info=True) raise error # Test case @patch.object(Account, "deposit") def test_atomicity_when_deposit_fails(self, deposit_mock): deposit_mock.side_effect = ValidationError("deposit failed") self.sender_account.balance = 300.0 self.sender_account.save() # Balance before attempting to transfer before_sender_balance = self.sender_account.balance with pytest.raises(ValidationError, match="deposit failed"): perform_atomic(self.transaction, self.sender_account, self.receiver_account, self.amount) self.sender_account.refresh_from_db() # Balance after attempting to transfer after_sender_balance = self.sender_account.balance.amount # assert that the sender balanc didn't change assert before_sender_balance == after_sender_balance
问题原因与修复方案
1. 硬编码扣款金额与测试场景冲突
原代码中硬编码sender_account.balance -= 1000,但测试里发送方初始余额仅300.0,直接减1000会导致余额负数,可能触发额外未捕获异常,干扰事务回滚逻辑。需改为使用传入的amount参数。
2. 断言时类型不匹配
before_sender_balance取的是balance对象,after_sender_balance取的是balance.amount数值,两者类型不同导致断言失败,需统一取值方式。
3. 事务内对象状态与数据库同步问题
调试时执行速度慢,对象缓存与数据库状态同步及时;正常运行时事务回滚后,需确保通过refresh_from_db()正确刷新对象状态(原测试已调用,但需配合前两点修复)。
修正后的代码
事务执行函数
import pytest from django.db import transaction from django.core.exceptions import ValidationError from cards.models import Account, Transaction import logging logger = logging.getLogger(__name__) def perform_atomic(txn, sender_account, receiver_account, amount): try: with transaction.atomic(): # 使用传入的amount替代硬编码值 sender_account.balance -= amount sender_account.save() receiver_account.deposit(amount) # 转账成功时更新事务状态 txn.status = Transaction.TRANSACTION_STATUS_SUCCESS txn.save() except Exception as error: txn.status = Transaction.TRANSACTION_STATUS_FAILED txn.save() logger.error(f"Failed Action. Reason[{error}]", exc_info=True) raise error
测试用例
@patch.object(Account, "deposit") def test_atomicity_when_deposit_fails(self, deposit_mock): deposit_mock.side_effect = ValidationError("deposit failed") self.amount = 100.0 # 设置合理的转账金额,小于初始余额 self.sender_account.balance = 300.0 self.sender_account.save() # 统一取数值类型 before_sender_balance = self.sender_account.balance.amount with pytest.raises(ValidationError, match="deposit failed"): perform_atomic(self.transaction, self.sender_account, self.receiver_account, self.amount) self.sender_account.refresh_from_db() after_sender_balance = self.sender_account.balance.amount assert before_sender_balance == after_sender_balance
内容的提问来源于stack exchange,提问作者Mai Elshiashi
相关产品推荐
相关产品推荐

