TransactionManagementError“ คุณไม่สามารถดำเนินการค้นหาจนสิ้นสุดบล็อก 'atomic'” ในขณะที่ใช้สัญญาณ แต่เฉพาะในระหว่างการทดสอบหน่วย


195

ฉันได้รับ TransactionManagementError เมื่อพยายามบันทึกตัวอย่างรุ่นผู้ใช้ Django และในสัญญาณหลังโพสต์บันทึกของฉันฉันบันทึกบางรุ่นที่มีผู้ใช้เป็นคีย์ต่างประเทศ

บริบทและข้อผิดพลาดค่อนข้างคล้ายกับคำถามนี้ 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()

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

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)

ตรวจสอบย้อนกลับ:

======================================================================
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.

----------------------------------------------------------------------

จากเอกสาร: "TestCase ไม่ตัดทอนตารางหลังจากการทดสอบ แต่จะแนบรหัสทดสอบในธุรกรรมฐานข้อมูลที่ย้อนกลับเมื่อสิ้นสุดการทดสอบทั้งสองยอมรับอย่างชัดเจนเช่น transaction.commit () และรายการโดยนัยที่อาจเกิดขึ้นจาก transaction.atomic () จะถูกแทนที่ด้วยการดำเนินการ nop ซึ่งเป็นการรับประกันว่าการย้อนกลับเมื่อสิ้นสุดการทดสอบจะคืนค่าฐานข้อมูลกลับสู่สถานะเริ่มต้น "
Gaurav Toshniwal

6
ฉันพบปัญหาของฉัน มีข้อยกเว้น IntegrityError เช่นนี้ "ลอง: ... ยกเว้น IntegrityError: ... " สิ่งที่ฉันต้องทำคือการใช้ transaction.atomic ภายใน try-block: "ลอง: กับ transaction.atomic (): .. . ยกเว้น IntegrityError: ... "ตอนนี้ทุกอย่างทำงานได้ดี
caio

docs.djangoproject.com/en/dev/topics/db/transactionsแล้วค้นหา "การห่ออะตอมมิกในการทดลอง / ยกเว้นบล็อกอนุญาตการจัดการข้อผิดพลาดด้านความเป็นธรรมชาติ:"
CamHart

คำตอบ:


238

ฉันพบปัญหาเดียวกันนี้ด้วยตัวเอง สิ่งนี้เกิดขึ้นจากมุมมองที่แปลกใหม่ในการจัดการธุรกรรมใน 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()

ใน Django 1.4 วิธีนี้ใช้ได้ดี อย่างไรก็ตามใน Django 1.5 / 1.6 การทดสอบแต่ละรายการจะถูกห่อในธุรกรรมดังนั้นหากมีข้อยกเว้นเกิดขึ้นการทำธุรกรรมจะหยุดการทำธุรกรรมจนกว่าคุณจะย้อนกลับอย่างชัดเจน ดังนั้นการดำเนินการ ORM เพิ่มเติมใด ๆ ในธุรกรรมนั้นเช่นฉัน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

ซึ่งจะป้องกันข้อยกเว้นที่เกิดจากการทำธุรกรรมของ unittest ทั้งหมด


71
นอกจากนี้ให้พิจารณาเพียงแค่ประกาศคลาสทดสอบของคุณเป็น TransactionTestCase แทนที่จะเป็นเพียง TestCase
mkoistinen

1
โอ้ฉันพบเอกสารที่เกี่ยวข้องจากอีกคำถาม เอกสารที่นี่
yaobin

2
สำหรับฉันฉันแล้วมีtransaction.atomic()บล็อก แต่ฉันได้ข้อผิดพลาดนี้และผมก็มีความคิดว่าทำไม ฉันทำตามคำแนะนำของคำตอบและวางบล็อกอะตอมซ้อนกันภายในบล็อกอะตอมรอบ ๆ พื้นที่ที่มีปัญหา หลังจากนั้นมันให้ข้อผิดพลาดโดยละเอียดของข้อผิดพลาดด้านความสมบูรณ์ที่ฉันได้รับอนุญาตให้ฉันแก้ไขรหัสของฉันและทำสิ่งที่ฉันพยายามทำ
AlanSE

5
@mkoistinen TestCaseกำลังสืบทอดจากTransactionTestCaseจึงไม่จำเป็นต้องเปลี่ยนที่ SimpleTestCaseหากคุณไม่ได้ทำงานบนฐานข้อมูลที่ใช้ในการทดสอบ
bns

1
@ bns คุณไม่มีจุดแสดงความคิดเห็น ใช่TestCaseสืบทอดมาTransactionTestCaseแต่ลักษณะการทำงานของมันแตกต่างกันมาก: มันหุ้มวิธีทดสอบแต่ละรายการในธุรกรรม TransactionTestCaseในทางกลับกันอาจมีชื่อที่ทำให้เข้าใจผิด: มันตัดทอนตารางเพื่อรีเซ็ต db - การตั้งชื่อดูเหมือนจะสะท้อนให้เห็นว่าคุณสามารถทดสอบการทำธุรกรรมภายในการทดสอบไม่ใช่ว่าการทดสอบถูกห่อเป็นธุรกรรม!
CS

48

เนื่องจาก @mkoistinen ไม่เคยแสดงความคิดเห็นคำตอบของฉันฉันจะโพสต์ข้อเสนอแนะของเขาเพื่อให้ผู้คนไม่ต้องขุดผ่านความคิดเห็น

พิจารณาเพียงแค่ประกาศคลาสทดสอบของคุณเป็น TransactionTestCase แทนที่จะเป็นเพียง TestCase

จากเอกสาร : TransactionTestCase อาจเรียกคอมมิชชันและย้อนกลับและสังเกตผลของการเรียกเหล่านี้บนฐานข้อมูล


2
+1 สำหรับสิ่งนี้ แต่ตามที่เอกสารบอกว่า "คลาส TestCase ของ Django เป็นคลาสย่อยที่ใช้กันทั่วไปของ TransactionTestCase" เพื่อตอบคำถามเดิมเราไม่ควรใช้ SimpleTestCase แทน TestCase ใช่หรือไม่ SimpleTestCase ไม่มีคุณสมบัติฐานข้อมูลอะตอม
daigorocub

@daigorocub เมื่อสืบทอดจากSimpleTestCase, ต้องเพิ่มภายในระดับการทดสอบดังนั้นมันจะไม่คายallow_database_queries = True AssertionError("Database queries aren't allowed in SimpleTestCase...",)
CristiFati

นี่คือคำตอบที่ดีที่สุดสำหรับฉันเมื่อฉันพยายามทดสอบ integrityerror ที่จะเพิ่มขึ้นจากนั้นฉันก็ต้องเรียกใช้คำสั่งบันทึกฐานข้อมูลเพิ่มเติม
Kim Stacks

8

หากใช้ pytest-django คุณสามารถส่งtransaction=Trueต่อไปยังผู้django_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

ฉันมีปัญหากับโซลูชันนี้ฉันมีข้อมูลเริ่มต้นใน DB ของฉัน (เพิ่มโดยการย้ายข้อมูล) โซลูชันนี้จะล้างฐานข้อมูลดังนั้นการทดสอบอื่น ๆ ขึ้นอยู่กับข้อมูลเริ่มต้นนี้เริ่มที่จะล้มเหลว
abumalick

1

สำหรับฉันการแก้ไขที่เสนอไม่ทำงาน ในการทดสอบของฉันฉันเปิดบางกระบวนการย่อยด้วยPopenเพื่อวิเคราะห์ / การย้ายข้อมูลไปมา (เช่นการทดสอบหนึ่งรายการจะตรวจสอบว่าไม่มีการเปลี่ยนแปลงรูปแบบหรือไม่)

สำหรับฉันแล้ว subclassing จากSimpleTestCaseแทนที่จะเป็นTestCaseทำแบบนั้น

สังเกตได้ว่า SimpleTestCaseไม่อนุญาตให้ใช้ฐานข้อมูล

แม้ว่านี่จะไม่ตอบคำถามเดิม แต่ฉันหวังว่านี่จะช่วยให้บางคนอยู่ดี


1

นี่เป็นอีกวิธีในการทำตามคำตอบของคำถามนี้:

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

0

ฉันได้รับข้อผิดพลาดนี้จากการเรียกใช้การทดสอบหน่วยในฟังก์ชั่น create_test_data ของฉันโดยใช้ django 1.9.7 มันทำงานใน django รุ่นก่อนหน้า

ดูเหมือนว่านี้:

cls.localauth,_ = Organisation.objects.get_or_create(organisation_type=cls.orgtypeLA, name='LA for test', email_general='test@test.com', address='test', postcode='test', telephone='test')
cls.chamber,_ = Organisation.objects.get_or_create(organisation_type=cls.orgtypeC, name='chamber for test', email_general='test@test.com', address='test', postcode='test', telephone='test')
cls.lawfirm,_ = Organisation.objects.get_or_create(organisation_type=cls.orgtypeL, name='lawfirm for test', email_general='test@test.com', address='test', postcode='test', telephone='test')

cls.chamber.active = True
cls.chamber.save()

cls.localauth.active = True
cls.localauth.save()    <---- error here

cls.lawfirm.active = True
cls.lawfirm.save()

โซลูชันของฉันคือใช้ update_or_create แทน:

cls.localauth,_ = Organisation.objects.update_or_create(organisation_type=cls.orgtypeLA, name='LA for test', email_general='test@test.com', address='test', postcode='test', telephone='test', defaults={'active': True})
cls.chamber,_ = Organisation.objects.update_or_create(organisation_type=cls.orgtypeC, name='chamber for test', email_general='test@test.com', address='test', postcode='test', telephone='test', defaults={'active': True})
cls.lawfirm,_ = Organisation.objects.update_or_create(organisation_type=cls.orgtypeL, name='lawfirm for test', email_general='test@test.com', address='test', postcode='test', telephone='test', defaults={'active': True})

1
get_or_create()ทำงานได้ดีดูเหมือนว่าจะเป็น. ssave () มันไม่ชอบภายใน transaction.atomic () ฟังก์ชั่นการตกแต่ง
Timothy Makobu

0

ฉันมีปัญหาเดียวกัน แต่ with transaction.atomic()และTransactionTestCaseไม่ได้ผลสำหรับฉัน

python manage.py test -rแทนที่จะpython manage.py testเป็นสิ่งที่ดีสำหรับฉันบางทีลำดับของการดำเนินการเป็นสิ่งสำคัญ

จากนั้นฉันหาเอกสารเกี่ยวกับ คำสั่งซื้อที่ดำเนินการทดสอบกล่าวถึงการทดสอบที่จะดำเนินการก่อน

ดังนั้นฉันใช้ TestCase สำหรับการโต้ตอบกับฐานข้อมูลunittest.TestCaseสำหรับการทดสอบทั่วไปอื่น ๆ ใช้งานได้แล้วตอนนี้!


0

คำตอบของ @kdazzle ถูกต้อง ฉันไม่ได้ลองเพราะคนบอกว่า 'คลาส TestCase ของ Django เป็น subclass ที่ใช้บ่อยกว่าของ TransactionTestCase' ดังนั้นฉันคิดว่ามันใช้เหมือนกันอย่างใดอย่างหนึ่ง แต่บล็อกของ Jahongir Rahmonovอธิบายได้ดีกว่า:

คลาส TestCase ล้อมรอบการทดสอบภายในสองบล็อกแบบซ้อน atomic (): หนึ่งคลาสสำหรับทั้งคลาสและอีกหนึ่งคลาสสำหรับการทดสอบแต่ละครั้ง นี่คือที่ที่ควรใช้ TransactionTestCase มันไม่ได้ห่อหุ้มการทดสอบด้วยบล็อก atomic () และทำให้คุณสามารถทดสอบวิธีพิเศษของคุณที่ต้องการการทำธุรกรรมโดยไม่มีปัญหาใด ๆ

แก้ไข: มันใช้งานไม่ได้ฉันคิดว่าใช่ แต่ไม่

ใน 4 ปีพวกเขาสามารถแก้ไขสิ่งนี้ .......................................


0
def test_wrong_user_country_db_constraint(self):
        """
        Check whether or not DB constraint doesnt allow to save wrong country code in DB.
        """
        self.test_user_data['user_country'] = 'XX'
        expected_constraint_name = "country_code_within_list_of_countries_check"

        with transaction.atomic():
            with self.assertRaisesRegex(IntegrityError, expected_constraint_name) as cm:
                get_user_model().objects.create_user(**self.test_user_data)

        self.assertFalse(
            get_user_model().objects.filter(email=self.test_user_data['email']).exists()
        )
with transaction.atomic() seems do the job correct

-4

ฉันมีปัญหาเดียวกัน

ในกรณีของฉันฉันทำเช่นนี้

author.tasks.add(tasks)

ดังนั้นแปลงเป็น

author.tasks.add(*tasks)

ลบข้อผิดพลาดนั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.