5

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

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())

测试夹具 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())

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

4

1 回答 1

3

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

TransactionTestCase显然知道它正在使用事务,并且因为它是 django 的东西,所以它知道它需要为每个测试重新添加固定装置,但是 pytest 通常不知道 django 的需求,因此它无法知道它需要重新运行您的固定装置django_db_setup——就它而言,它只需要运行一次,因为它是会话范围的。

您有以下选择:

  1. 使用范围较低的夹具,可能是function评论中建议的范围。但这可能是可选的,并且将在事务中运行,因此在测试完成后将被删除。
  2. 编写一个智能/django 感知的夹具,并通过检测测试何时使用事务来知道何时需要重新填充该数据。但是您需要确保正在使用的数据库连接不在事务中。我已经在 django 1.11 上完成了这项工作,它工作正常,尽管升级后可能需要修复。看起来像这样:
from unittest.mock import patch

from django.core.management import call_command
from django.db import DEFAULT_DB_ALIAS, ConnectionHandler

import pytest


_need_data_load = True


@pytest.fixture(autouse=True)
def auto_loaddata(django_db_blocker, request):
    global _need_data_load
    if _need_data_load:
        # Use a separate DB connection to ensure we're not in a transaction.
        con_h = ConnectionHandler()
        try:
            def_con = con_h[DEFAULT_DB_ALIAS]
            # we still need to unblock the database because that's a test level
            # constraint which simply monkey patches the database access methods
            # in django to prevent access.
            # 
            # Also note here we need to use the correct connection object
            # rather than any default, and so I'm assuming the command
            # imports `from django.db import connection` so I can swap it.
            with django_db_blocker.unblock(), patch(
                'path.to.your.command.modules.connection', def_con
            ):
                call_command('loaddata')
        finally:
            con_h.close_all()
        _need_auto_sql = False

    using_transactional_db = (
        'transactional_db' in request.fixturenames
        or 'live_server' in request.fixturenames
    )
    if using_transactional_db:
        # if we're using a transactional db then we will dump the whole thing
        # on teardown, so need to flag that we should set it up again after.
        _need_data_load = True
于 2019-08-25T19:09:28.083 回答