TransactionManagementError"在使用信号时,您不能在"原子"块结束之前执行查询,但仅在单元测试期间执行

Gau*_*wal 172 python django unit-testing django-signals

我在尝试保存Django User模型实例时遇到TransactionManagementError,并在其post_save信号中,我保存了一些将用户作为外键的模型.

在使用信号时,上下文和错误与此问题django TransactionManagementError非常相似

但是,在这种情况下,错误仅在单元测试时发生.

它在手动测试中运行良好,但单元测试失败.

有什么我想念的吗?

以下是代码段:

views.py

@csrf_exempt
def mobileRegister(request):
    if request.method == 'GET':
        response = {"error": "GET request not accepted!!"}
        return HttpResponse(json.dumps(response), content_type="application/json",status=500)
    elif request.method == 'POST':
        postdata = json.loads(request.body)
        try:
            # Get POST data which is to be used to save the user
            username = postdata.get('phone')
            password = postdata.get('password')
            email = postdata.get('email',"")
            first_name = postdata.get('first_name',"")
            last_name = postdata.get('last_name',"")
            user = User(username=username, email=email,
                        first_name=first_name, last_name=last_name)
            user._company = postdata.get('company',None)
            user._country_code = postdata.get('country_code',"+91")
            user.is_verified=True
            user._gcm_reg_id = postdata.get('reg_id',None)
            user._gcm_device_id = postdata.get('device_id',None)
            # Set Password for the user
            user.set_password(password)
            # Save the user
            user.save()
Run Code Online (Sandbox Code Playgroud)

signal.py

def create_user_profile(sender, instance, created, **kwargs):
    if created:
        company = None
        companycontact = None
        try:   # Try to make userprofile with company and country code provided
            user = User.objects.get(id=instance.id)
            rand_pass = random.randint(1000, 9999)
            company = Company.objects.get_or_create(name=instance._company,user=user)
            companycontact = CompanyContact.objects.get_or_create(contact_type="Owner",company=company,contact_number=instance.username)
            profile = UserProfile.objects.get_or_create(user=instance,phone=instance.username,verification_code=rand_pass,company=company,country_code=instance._country_code)
            gcmDevice = GCMDevice.objects.create(registration_id=instance._gcm_reg_id,device_id=instance._gcm_reg_id,user=instance)
        except Exception, e:
            pass
Run Code Online (Sandbox Code Playgroud)

tests.py

class AuthTestCase(TestCase):
    fixtures = ['nextgencatalogs/fixtures.json']
    def setUp(self):
        self.user_data={
            "phone":"0000000000",
            "password":"123",
            "first_name":"Gaurav",
            "last_name":"Toshniwal"
            }

    def test_registration_api_get(self):
        response = self.client.get("/mobileRegister/")
        self.assertEqual(response.status_code,500)

    def test_registration_api_post(self):
        response = self.client.post(path="/mobileRegister/",
                                    data=json.dumps(self.user_data),
                                    content_type="application/json")
        self.assertEqual(response.status_code,201)
        self.user_data['username']=self.user_data['phone']
        user = User.objects.get(username=self.user_data['username'])
        # Check if the company was created
        company = Company.objects.get(user__username=self.user_data['phone'])
        self.assertIsInstance(company,Company)
        # Check if the owner's contact is the same as the user's phone number
        company_contact = CompanyContact.objects.get(company=company,contact_type="owner")
        self.assertEqual(user.username,company_contact[0].contact_number)
Run Code Online (Sandbox Code Playgroud)

追溯:

======================================================================
ERROR: test_registration_api_post (nextgencatalogs.apps.catalogsapp.tests.AuthTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/nextgencatalogs/apps/catalogsapp/tests.py", line 29, in test_registration_api_post
    user = User.objects.get(username=self.user_data['username'])
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/manager.py", line 151, in get
    return self.get_queryset().get(*args, **kwargs)
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/query.py", line 301, in get
    num = len(clone)
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/query.py", line 77, in __len__
    self._fetch_all()
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/query.py", line 854, in _fetch_all
    self._result_cache = list(self.iterator())
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/query.py", line 220, in iterator
    for row in compiler.results_iter():
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 710, in results_iter
    for rows in self.execute_sql(MULTI):
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 781, in execute_sql
    cursor.execute(sql, params)
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/backends/util.py", line 47, in execute
    self.db.validate_no_broken_transaction()
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/backends/__init__.py", line 365, in validate_no_broken_transaction
    "An error occurred in the current transaction. You can't "
TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.

----------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

Cer*_*rin 216

我自己遇到了同样的问题.这是由于在较新版本的Django中处理事务的怪癖以及故意触发异常的unittest引起的.

我有一个单元测试,检查以确保通过故意触发IntegrityError异常强制执行唯一列约束:

def test_constraint(self):
    try:
        # Duplicates should be prevented.
        models.Question.objects.create(domain=self.domain, slug='barks')
        self.fail('Duplicate question allowed.')
    except IntegrityError:
        pass

    do_more_model_stuff()
Run Code Online (Sandbox Code Playgroud)

在Django 1.4中,这很好用.但是,在Django 1.5/1.6中,每个测试都包含在一个事务中,因此如果发生异常,它会中断事务,直到您显式回滚它为止.因此,该事务中的任何进一步的ORM操作(例如my do_more_model_stuff())将因该django.db.transaction.TransactionManagementError异常而失败.

与评论中提到的caio一样,解决方案是使用以下方式捕获您的异常transaction.atomic:

from django.db import transaction
def test_constraint(self):
    try:
        # Duplicates should be prevented.
        with transaction.atomic():
            models.Question.objects.create(domain=self.domain, slug='barks')
        self.fail('Duplicate question allowed.')
    except IntegrityError:
        pass
Run Code Online (Sandbox Code Playgroud)

这将防止故意抛出的异常破坏整个unittest的事务.

  • 还要考虑将您的测试类声明为TransactionTestCase而不仅仅是TestCase. (60认同)
  • `with transaction.atomic():`做了诀窍,谢谢! (5认同)
  • @mkoistinen`TestCase`继承自`TransactionTestCase`,因此无需更改.如果在测试中不使用DB,请使用`SimpleTestCase`. (5认同)
  • 请注意,如果您需要使用“setUpTestData”,则不能将其与“TransactionTestCase”一起使用,而需要与“TestCase”一起使用。在测试唯一组的完整性错误时,我必须将“save”调用包装在“withatomic()”块内 (4认同)
  • @bns 你没有抓住评论的重点。是的 `TestCase` 继承自 `TransactionTestCase`,但它的行为完全不同:它将每个测试方法包装在一个事务中。另一方面,`TransactionTestCase` 的命名可能具有误导性:它截断表以重置数据库——命名似乎反映了您可以在测试中测试事务,而不是将测试包装为事务! (3认同)
  • 对我来说,我已经有一个`transaction.atomic()`块,但是我遇到了这个错误,我也不知道为什么。我接受了这个答案的建议,并在故障区域周围的原子块内放置了一个_nested_原子块。之后,它给出了我遇到的完整性错误的详细错误,使我可以修复代码并执行我想做的事情。 (2认同)

kda*_*zle 42

由于@mkoistinen从未发表评论,回答,我会发布他的建议,以便人们不必深入评论.

考虑将您的测试类声明为TransactionTestCase而不仅仅是TestCase.

文档:TransactionTestCase可以调用commit和rollback并观察这些调用对数据库的影响.

  • 为此+1,但正如文档所说,"Django的TestCase类是TransactionTestCase的一个更常用的子类".要回答原始问题,我们不应该使用SimpleTestCase而不是TestCase吗?SimpleTestCase没有原子数据库功能. (2认同)
  • 这是最适合我的答案,因为我试图测试完整性错误,然后我需要运行更多数据库保存查询 (2认同)
  • 重要的是要记住,“TransactionTestCase”可能比传统的“TestCase”慢得多。 (2认同)

小智 7

根据对这个问题的回答,这是另一种方法:

with transaction.atomic():
    self.assertRaises(IntegrityError, models.Question.objects.create, **{'domain':self.domain, 'slug':'barks'})
Run Code Online (Sandbox Code Playgroud)


frm*_*ryr 5

如果使用pytest-django,您可以传递transaction=Truedjango_db装饰器以避免此错误。

参见https://pytest-django.readthedocs.io/en/latest/database.html#testing-transactions

Django本身具有TransactionTestCase,可让您测试事务,并将在测试之间刷新数据库以隔离它们。不利的一面是,由于需要刷新数据库,因此这些测试的建立速度要慢得多。pytest-django也支持这种测试风格,您可以使用django_db标记的参数进行选择:

@pytest.mark.django_db(transaction=True)
def test_spam():
    pass  # test relying on transactions
Run Code Online (Sandbox Code Playgroud)