Django+pytest测试中实现describe块内数据持久化的方法
在pytest-describe块中共享Django数据库上下文的解决方案
我完全懂你想要的效果:让同一个describe块里的测试共享数据库状态,只在整个块执行完毕后再统一回滚,而不是每个测试单独回滚。这确实能让describe风格的测试更连贯,还能提升执行效率。
首先说下为什么官方的db_access_without_rollback_and_truncate没生效——这个夹具是模块/会话级的,它会彻底关闭自动回滚和截断,会导致不同describe块之间的数据互相污染,并不是我们需要的「块级隔离+共享」。我们要的是针对单个describe块的事务控制。
解决方案:自定义Class作用域的事务夹具
pytest-describe的每个describe块会被pytest自动转换成一个测试类,所以我们可以利用class作用域的夹具来实现块级的事务管理:
- 先定义一个自定义夹具,负责在
describe块启动时开启事务,结束时统一回滚:
import pytest from django.db import transaction @pytest.fixture(scope="class") def db_transaction_scope(): # 关闭自动提交,开启一个顶层事务 transaction.set_autocommit(False) try: # 让describe块内的所有测试都在这个事务中执行 yield finally: # 块执行完毕后回滚整个事务,恢复初始状态 transaction.rollback() # 恢复自动提交,不影响其他测试 transaction.set_autocommit(True)
- 修改你的测试代码,使用这个夹具并配置
django_db允许手动事务管理:
import pytest from django.contrib.auth import get_user_model User = get_user_model() # 应用自定义夹具,并告诉pytest-django我们要手动管理事务 @pytest.mark.usefixtures("db_transaction_scope") @pytest.mark.django_db(transaction=True) def describe_users(): email = 'foo@example.com' def test_create_a_user_and_it_exists(): User.objects.create(email=email) assert User.objects.filter(email=email).exists() def test_the_user_keeps_to_exist(): assert User.objects.filter(email=email).exists()
为什么这能生效?
db_transaction_scope是class作用域,正好对应一个describe块的生命周期:在块的第一个测试前开启事务,所有测试都在这个事务内操作数据,最后一个测试结束后统一回滚。pytest.mark.django_db(transaction=True)是关键:它告诉pytest-django不要为每个测试自动创建和回滚事务,把事务控制权交给我们的自定义夹具。
注意事项
- 这种方式只适合有顺序依赖的测试(毕竟你就是要共享上下文),而pytest-describe会按测试定义的顺序执行,所以完全适配。
- 不要在这个块里执行DDL操作(比如创建/修改表结构),因为Django的事务回滚不支持DDL,会导致数据无法回滚。
- 如果有嵌套的
describe块,你可能需要调整夹具的作用域为module或者针对嵌套块单独处理,但大部分场景下class作用域足够用。
内容的提问来源于stack exchange,提问作者Mattia Larentis
相关产品推荐
相关产品推荐

