Django 2.0项目单元测试因IntegrityError失败求助(PostgreSQL环境)
Hey there! Let's figure out why your Django 2.0 unit tests are failing with PostgreSQL 10.1 (but working fine with SQLite) — especially since the tests pass individually but break during teardown. This is a common gotcha with database-specific behavior, so let's break down the most likely causes and fixes:
1. Transaction Behavior Differences Between Databases
PostgreSQL has way stricter transaction rules than SQLite. Django wraps each test in a transaction that gets rolled back after the test finishes, but if your change-tracking model uses signals (like post_save or post_delete) that run database operations outside the test's transaction, PostgreSQL will enforce integrity constraints during rollback that SQLite ignores.
- Audit your signal handlers: If you're using signals to create/update your tracking entries, make sure they're tied to the same transaction as the model they're tracking. For example, if you have a signal that creates a
ChangeLogentry when aProductis saved, wrap the database operation intransaction.on_commit()to ensure it only runs after the test's transaction commits (and thus won't cause issues during rollback):from django.db import transaction from django.db.models.signals import post_save from django.dispatch import receiver from .models import Product, ChangeLog @receiver(post_save, sender=Product) def log_product_change(sender, instance, created, **kwargs): def create_log_entry(): ChangeLog.objects.create( product_id=instance.id, action="created" if created else "updated" ) # Wait until the transaction commits to create the log transaction.on_commit(create_log_entry)
2. Teardown Order & Foreign Key Constraints
PostgreSQL enforces foreign key constraints rigorously, while SQLite doesn't (unless you explicitly enable foreign_keys in your settings). During teardown, Django might try to delete your main model instances before the tracking model instances, triggering an integrity error because the tracking entries still reference the main model.
- Explicitly delete tracking entries first: Override your test class's
tearDown()method to clean up the tracking model before the main models. This ensures you don't hit foreign key conflicts:from django.test import TestCase from .models import ChangeLog class ProductTests(TestCase): def tearDown(self): # Delete tracking entries first to avoid FK violations ChangeLog.objects.all().delete() # Then run the default teardown super().tearDown() - Double-check
on_deletebehavior: Make sure the foreign key in your tracking model useson_delete=models.CASCADE(so deleting a main model instance deletes its tracking entries automatically). Even with this, PostgreSQL's transaction rollback might not handle deletion order the same way SQLite does, so explicit teardown cleanup is still a safe bet.
3. Test Isolation Gaps
When running tests in a suite, PostgreSQL's stricter isolation levels can expose shared state issues that SQLite ignores. If one test leaves behind data that conflicts with another during teardown, you'll get integrity errors.
- Use the right test class: Ensure your tests inherit from
TestCase(which uses transactions for isolation) instead ofSimpleTestCase. If you're already usingTestCase, try switching toTransactionTestCase— it's slower, but it resets the database between tests by truncating tables instead of rolling back transactions, which can avoid teardown conflicts. - Verify setup/cleanup logic: Make sure any test data you create in
setUp()is properly contained within the test's transaction. Avoid using global fixtures or data that persists across tests unless you're explicitly cleaning it up.
4. PostgreSQL-Specific Constraint Enforcement
PostgreSQL enforces constraints like UNIQUE or NOT NULL more strictly than SQLite, even during transaction rollback. If your tracking model has unique constraints (e.g., a unique combination of model_id and change_timestamp), rollback might leave orphaned entries that conflict with the next test's setup.
- Audit your tracking model's constraints: Check if any
unique_togetherorUniqueConstraintclauses could be causing conflicts during teardown. If so, you might need to adjust the constraint or add cleanup logic to remove conflicting entries before each test runs. - Temporarily defer constraints (last resort): If you can't pinpoint the exact constraint, you can tell PostgreSQL to defer all constraint checks during teardown. This is a workaround, not a fix, but it can help you debug further:
from django.db import connection def tearDown(self): with connection.cursor() as cursor: cursor.execute("SET CONSTRAINTS ALL DEFERRED;") super().tearDown()
内容的提问来源于stack exchange,提问作者luc

