TransactionManagementError “Sinyalleri kullanırken 'atomik' bloğun” sonuna kadar sorgu yürütemezsiniz, ancak sadece Birim Testi sırasında


195

Bir Django kullanıcı modeli örneği kaydetmeye çalışırken TransactionManagementError alıyorum ve onun post_save sinyal, ben yabancı anahtar olarak kullanıcı olan bazı modelleri kaydediyorum.

Bağlam ve hata bu soruya oldukça benzer django TransactionManagementError sinyalleri kullanırken

Ancak, bu durumda, hata yalnızca birim sınama sırasında ortaya çıkar .

Manuel testte iyi çalışır, ancak birim testleri başarısız olur.

Kaçırdığım bir şey var mı?

Kod parçacıkları şunlardır:

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)

Geri iz:

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

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

Belgelerden: "Diğer taraftan, bir TestCase, bir testten sonra tabloları kesmez. Bunun yerine, test kodunu testin sonunda geri alınan bir veritabanı işlemine ekler. Her ikisi de açık işlem.commit gibi işler () ve transaction.atomic () öğesinin neden olabileceği örtük olanların yerini bir nop işlemi alır. Bu, testin sonundaki geri almanın veritabanını ilk durumuna geri yüklemesini garanti eder. "
Gaurav Toshniwal

6
Sorunumu buldum. Bunun gibi bir IntegrityError istisnası vardı "try: ... IntegrityError: ..." dışında yapmam gereken try-block içinde transaction.atomic kullanmaktır: "try: with transaction.atomic (): .. . "IntegrityError: ..." şimdi her şey yolunda gidiyor.
caio

docs.djangoproject.com/tr/dev/topics/db/transactions ve sonra "Bir denemede / blokta atomik
sarma

Yanıtlar:


238

Ben de aynı problemle karşılaştım. Bunun nedeni, Django'nun yeni sürümlerinde işlemlerin nasıl yürütüldüğüne ilişkin bir tuhaflık ve bir istisna oluşturan kasıtlı bir birim test ile ilgilidir.

Bir IntegrityError istisnasını kasıtlı olarak tetikleyerek benzersiz bir sütun kısıtlaması uygulandığından emin olmak için kontrol edilen bir unittest vardı:

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'te, bu iyi çalışıyor. Ancak, Django 1.5 / 1.6'da her sınama bir işleme sarılır, bu nedenle bir istisna oluşursa, siz açıkça geri dönene kadar işlemi kırar. Bu nedenle, bu işlemdeki my gibi başka ORM işlemleri do_more_model_stuff()bu django.db.transaction.TransactionManagementErroristisna dışında başarısız olur .

Yorumlarda adı geçen caio gibi, çözüm de istisnanızı şöyle yakalamaktır 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

Bu, özel olarak atılan istisnanın tüm unittest işlemini kırmasını önleyecektir.


71
Ayrıca, test sınıfınızı yalnızca TestCase yerine TransactionTestCase olarak bildirmeyi de düşünün.
mkoistinen

1
Ah, ilgili belgeyi başka bir sorudan buldum . Belge burada .
yaobin

2
Benim için zaten bir transaction.atomic()bloğum vardı , ama bu hatayı aldım ve neden olduğu hakkında hiçbir fikrim yoktu. Bu cevabın tavsiyesini aldım ve atom bloğumun içine sorunlu bölgenin etrafına iç içe bir atom bloğu koydum . Bundan sonra, vurduğum bütünlük hatasının ayrıntılı bir hatasını verdi, kodumu düzeltmeme ve yapmaya çalıştığım şeyi yapmama izin verdi.
AlanSE

5
@ mkoistinen kalıtsaldır TestCase, TransactionTestCasedeğiştirmeye gerek yoktur. Test kullanımında DB üzerinde çalışmazsanız SimpleTestCase.
bns

1
@bns yorumun noktasını kaçırıyorsunuz. Evet, TestCasemiras alır TransactionTestCaseancak davranışı oldukça farklıdır: her test yöntemini bir işlemde sarar. TransactionTestCaseÖte yandan, belki yanıltıcı bir şekilde adlandırılır: db sıfırlamak için tabloları keser - adlandırma testi bir işlem olarak sarılmış değil, bir test içinde işlemleri test edebilirsiniz yansıtıyor gibi görünüyor!
CS

48

@Mkoistinen yorumunu , cevabını hiç yapmadığından , önerilerini yayınlayacağım, böylece insanlar yorumları kazmak zorunda kalmayacaklar.

sadece test sınıfınızı TestCase yerine TransactionTestCase olarak bildirmeyi düşünün.

Gönderen docs : A TransactionTestCase taahhüt arayıp geri alma ve veritabanında bu aramaların etkilerini gözlemek.


2
Bunun için +1, ancak dokümanların dediği gibi "Django'nun TestCase sınıfı, TransactionTestCase'in daha yaygın olarak kullanılan bir alt sınıfıdır". Orijinal soruyu cevaplamak için TestCase yerine SimpleTestCase kullanmamalı mıyız? SimpleTestCase atomik veritabanı özelliklerine sahip değildir.
daigorocub

devralan zaman @daigorocub SimpleTestCase, allow_database_queries = Truebir tükürmek yok bu yüzden test sınıf içinde eklenmelidir AssertionError("Database queries aren't allowed in SimpleTestCase...",).
CristiFati

Dürüstlük hatası için test etmeye çalıştığım için benim için en iyi olan cevap bu olacak ve daha sonra daha fazla veritabanı kaydetme sorgusu çalıştırmam gerekiyordu
Kim Stacks

8

Pytest-django kullanıyorsanız transaction=True, django_dbbu hatayı önlemek için dekoratöre geçebilirsiniz .

Bkz. Https://pytest-django.readthedocs.io/en/latest/database.html#testing-transactions

Django'nun kendisi, işlemleri test etmenizi sağlayan ve bunları izole etmek için testler arasında veritabanını temizleyecek TransactionTestCase'e sahiptir. Bunun dezavantajı, bu testlerin veritabanının gerekli yıkanması nedeniyle kurulması çok daha yavaş olmasıdır. pytest-django ayrıca django_db işaretine bir argüman kullanarak seçebileceğiniz bu test stilini de destekler:

@pytest.mark.django_db(transaction=True)
def test_spam():
    pass  # test relying on transactions

Bu çözüm ile ilgili bir sorun vardı, benim DB (geçişler tarafından eklenen) ilk veri vardı. Bu çözüm veritabanını yıkar, böylece bu ilk verilere bağlı diğer testler başarısız olmaya başlar.
abumalick

1

Benim için önerilen düzeltmeler işe yaramadı. Testlerimde, Popengeçişleri analiz etmek / tüy dökmek için bazı alt süreçler açıyorum (örn. Bir test, model değişikliği olup olmadığını kontrol eder).

Benim için SimpleTestCaseyerine alt sınıflama TestCasehile yaptı.

SimpleTestCaseVeritabanının kullanılmasına izin vermediğini unutmayın .

Bu asıl soruya cevap vermese de, umarım bu bazı insanlara yine de yardımcı olur.


1

İşte bu sorunun cevabına dayanarak bunu yapmanın başka bir yolu:

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

0

Bu hata django 1.9.7 kullanarak benim create_test_data işlevinde birim sınama çalıştırıyordu. Django'nun önceki sürümlerinde çalıştı.

Şöyle görünüyordu:

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

Benim çözümüm yerine update_or_create kullanmaktı:

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()yanı sıra çalışır, öyle görünüyor.
Timothy Makobu

0

Ben aynı sorunu var, ama with transaction.atomic()ve TransactionTestCasebenim için işe yaramadı.

python manage.py test -ryerine python manage.py testbenim için uygun, belki yürütme sırası çok önemlidir

daha sonra testlerin yapıldığı Order hakkında bir dokümanı buluyorum , önce hangi testin çalışacağından bahsediyor.

Yani, ben veritabanı etkileşimi için TestCase kullanın, unittest.TestCasediğer basit test için, şimdi çalışıyor!


0

@Kdazzle cevabı doğru. Ben denemedim çünkü insanlar 'Django's TestCase sınıfı TransactionTestCase daha yaygın olarak kullanılan bir alt sınıf' olduğunu söyledi, bu yüzden aynı ya da başka bir kullanım olduğunu düşündüm. Ancak Jahongir Rahmonov'un blogu daha iyi açıkladı:

TestCase sınıfı testleri iki iç içe atomik () bloğu içine sarar: biri tüm sınıf için ve diğeri her test için. TransactionTestCase burada kullanılmalıdır. Testleri atomic () bloğu ile sarmaz ve böylece sorunsuz bir işlem gerektiren özel yöntemlerinizi test edebilirsiniz.

EDIT: Bu işe yaramadı, evet düşündüm, ama HAYIR.

4 yıl içinde bunu düzeltebilirlerdi .......................................


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

Aynı sorunu yaşadım.

Benim durumumda bunu yapıyordum

author.tasks.add(tasks)

yani

author.tasks.add(*tasks)

Bu hata kaldırıldı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.