pytest-django 数据库初始化似乎没有重新加载数据库

pet*_*ter 5 python pytest django-unittest pytest-django

我们将情况归纳为以下几点:

import pytest
from django.core.management import call_command
from foo import bar

@pytest.fixture(scope='session')
def django_db_setup(django_db_setup, django_db_blocker):
    LOGGER.info('ran call_command')
    with django_db_blocker.unblock():
        call_command('loaddata', 'XXX.json')

@pytest.mark.django_db(transaction=True)
def test_t1():
    assert len(bar.objects.all())

@pytest.mark.django_db(transaction=True)
def test_t2():
    assert len(bar.objects.all())
Run Code Online (Sandbox Code Playgroud)

测试夹具 XXX.json 包括一个栏。第一个测试 (test_t1) 成功。第二个测试 (test_t2) 失败。似乎 transaction=True 属性不会导致使用来自测试装置的数据重新初始化数据库。

如果改为使用来自 unittest 的 TransactionTestCase,则初始化发生在类中的每个测试用例之前,并且所有测试都成功。

from django.test import TransactionTestCase

from foo import bar

class TestOne(TransactionTestCase):

    fixtures = ['XXX.json']

    def test_tc1(self):
        assert len(bar.objects.all())

    def test_tc2(self):
        assert len(bar.objects.all())
        objs = bar.objects.all()
        for bar in objs:
            bar.delete()

    def test_tc3(self):
        assert len(bar.objects.all())
Run Code Online (Sandbox Code Playgroud)

对于为什么 pytest 示例不会为第二个测试用例重新初始化数据库的任何观点,我将不胜感激。

dap*_*azz 3

django_db_setup是会话范围的,因此仅在测试会话开始时运行一次。使用时transaction=True,数据库会在每次测试(包括第一次)后刷新,因此添加的任何数据django_db_setup都会被删除。

\n

TransactionTestCase显然知道它正在使用事务,并且因为它是 django 的东西,所以它知道它需要为每个测试重新添加固定装置,但 pytest 一般来说不知道 django 的需求,所以它没有办法知道它需要重新运行您的装置django_db_setup\xe2\x80\x93 就其而言,它只需要运行一次,因为它是会话范围的。

\n

您有以下选择:

\n
    \n
  1. 使用范围较低的固定装置,可能达到function评论中建议的范围。但这可能是选择加入的,并且将在事务中运行,因此在测试完成后将被删除。
  2. \n
  3. 编写一个智能/django 感知的装置,并通过检测测试何时使用事务来知道何时需要重新填充该数据。但您需要确保正在使用的数据库连接不在事务中。我已经在 django 1.11 上完成了此操作,并且工作正常,尽管升级后可能需要修复。看起来像这样:
  4. \n
\n
from unittest.mock import patch\n\nfrom django.core.management import call_command\nfrom django.db import DEFAULT_DB_ALIAS, ConnectionHandler\n\nimport pytest\n\n\n_need_data_load = True\n\n\n@pytest.fixture(autouse=True)\ndef auto_loaddata(django_db_blocker, request):\n    global _need_data_load\n    if _need_data_load:\n        # Use a separate DB connection to ensure we\'re not in a transaction.\n        con_h = ConnectionHandler()\n        try:\n            def_con = con_h[DEFAULT_DB_ALIAS]\n            # we still need to unblock the database because that\'s a test level\n            # constraint which simply monkey patches the database access methods\n            # in django to prevent access.\n            # \n            # Also note here we need to use the correct connection object\n            # rather than any default, and so I\'m assuming the command\n            # imports `from django.db import connection` so I can swap it.\n            with django_db_blocker.unblock(), patch(\n                \'path.to.your.command.modules.connection\', def_con\n            ):\n                call_command(\'loaddata\')\n        finally:\n            con_h.close_all()\n        _need_auto_sql = False\n\n    using_transactional_db = (\n        \'transactional_db\' in request.fixturenames\n        or \'live_server\' in request.fixturenames\n    )\n    if using_transactional_db:\n        # if we\'re using a transactional db then we will dump the whole thing\n        # on teardown, so need to flag that we should set it up again after.\n        _need_data_load = True\n
Run Code Online (Sandbox Code Playgroud)\n