Djangoユーザーモデルインスタンスを保存しようとすると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()
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.
----------------------------------------------------------------------
私もこの同じ問題にぶつかりました。これは、新しいバージョンの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では、各テストはトランザクションにラップされているため、例外が発生すると、明示的にロールバックするまでトランザクションが中断されます。したがって、そのトランザクションでのdo_more_model_stuff()
などのORM操作は、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
これにより、意図的にスローされた例外がユニットテストのトランザクション全体を壊すことを防ぎます。
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
私にとって、提案された修正は機能しませんでした。私のテストでは、Popen
を使用していくつかのサブプロセスを開き、移行を分析/リントします(たとえば、1つのテストでモデルの変更がないかどうかを確認します)。
私にとっては、SimpleTestCase
の代わりにTestCase
からサブクラス化することでうまくいきました。
SimpleTestCase
はデータベースの使用を許可しないことに注意してください。
これは元の質問には答えませんが、とにかくこれが一部の人々に役立つことを願っています。
同じ問題がありますが、with transaction.atomic()
とTransactionTestCase
は機能しませんでした。
python manage.py test -r
の代わりにpython manage.py test
で問題ありません。実行順序が重要な場合があります
次に、 テストが実行される順序 に関するドキュメントを見つけます、それはどのテストが最初に実行されるかについて言及しています。
したがって、データベースの相互作用にはTestCaseを使用し、他の簡単なテストにはunittest.TestCase
を使用します。
@kdazzleの答えは正しいです。 「DjangoのTestCaseクラスはTransactionTestCaseのより一般的に使用されるサブクラスである」と人々が言ったので、私はそれを試しませんでした。しかし、 Jahongir Rahmonovのブログ は、より適切に説明しています:
testCaseクラスは、2つのネストされたatomic()ブロック内でテストをラップします。1つはクラス全体用で、もう1つは各テスト用です。これは、TransactionTestCaseを使用する場所です。 atomic()ブロックでテストをラップしないため、トランザクションを必要とする特別なメソッドを問題なくテストできます。
編集:それは動作しませんでした、私ははい、しかしいいえと思った。
4年で彼らはこれを修正することができました.......................................
Django 1.9.7を使用してcreate_test_data関数で単体テストを実行すると、このエラーが発生しました。 Djangoの以前のバージョンで機能しました。
このように見えた:
cls.localauth,_ = Organisation.objects.get_or_create(organisation_type=cls.orgtypeLA, name='LA for test', email_general='[email protected]', address='test', postcode='test', telephone='test')
cls.chamber,_ = Organisation.objects.get_or_create(organisation_type=cls.orgtypeC, name='chamber for test', email_general='[email protected]', address='test', postcode='test', telephone='test')
cls.lawfirm,_ = Organisation.objects.get_or_create(organisation_type=cls.orgtypeL, name='lawfirm for test', email_general='[email protected]', 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='[email protected]', 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='[email protected]', 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='[email protected]', address='test', postcode='test', telephone='test', defaults={'active': True})