mirror of
https://github.com/django/django.git
synced 2025-10-23 21:59:11 +00:00
Fixed #31395 -- Made setUpTestData enforce in-memory data isolation.
Since it's introduction in Django 1.8 setUpTestData has been suffering from a documented but confusing caveat due to its sharing of attributes assigned during its execution with all test instances. By keeping track of class attributes assigned during the setUpTestData phase its possible to ensure only deep copies are provided to test instances on attribute retreival and prevent manual setUp gymnastic to work around the previous lack of in-memory data isolation. Thanks Adam Johnson for the extensive review.
This commit is contained in:
committed by
Mariusz Felisiak
parent
1dd96f731d
commit
3cf80d3fcf
@@ -1,7 +1,11 @@
|
||||
from django.db import IntegrityError, connections, transaction
|
||||
from django.test import TestCase, skipUnlessDBFeature
|
||||
from functools import wraps
|
||||
|
||||
from .models import Car, PossessedCar
|
||||
from django.db import IntegrityError, connections, transaction
|
||||
from django.test import TestCase, ignore_warnings, skipUnlessDBFeature
|
||||
from django.test.testcases import TestData
|
||||
from django.utils.deprecation import RemovedInDjango41Warning
|
||||
|
||||
from .models import Car, Person, PossessedCar
|
||||
|
||||
|
||||
class TestTestCase(TestCase):
|
||||
@@ -38,3 +42,95 @@ class TestTestCase(TestCase):
|
||||
)
|
||||
with self.assertRaisesMessage(AssertionError, message):
|
||||
Car.objects.using('other').get()
|
||||
|
||||
|
||||
class NonDeepCopyAble:
|
||||
def __deepcopy__(self, memo):
|
||||
raise TypeError
|
||||
|
||||
|
||||
def assert_no_queries(test):
|
||||
@wraps(test)
|
||||
def inner(self):
|
||||
with self.assertNumQueries(0):
|
||||
test(self)
|
||||
return inner
|
||||
|
||||
|
||||
class TestDataTests(TestCase):
|
||||
# setUpTestData re-assignment are also wrapped in TestData.
|
||||
jim_douglas = None
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.jim_douglas = Person.objects.create(name='Jim Douglas')
|
||||
cls.car = Car.objects.create(name='1963 Volkswagen Beetle')
|
||||
cls.herbie = cls.jim_douglas.possessed_cars.create(
|
||||
car=cls.car,
|
||||
belongs_to=cls.jim_douglas,
|
||||
)
|
||||
cls.non_deepcopy_able = NonDeepCopyAble()
|
||||
|
||||
@assert_no_queries
|
||||
def test_class_attribute_equality(self):
|
||||
"""Class level test data is equal to instance level test data."""
|
||||
self.assertEqual(self.jim_douglas, self.__class__.jim_douglas)
|
||||
|
||||
@assert_no_queries
|
||||
def test_class_attribute_identity(self):
|
||||
"""
|
||||
Class level test data is not identical to instance level test data.
|
||||
"""
|
||||
self.assertIsNot(self.jim_douglas, self.__class__.jim_douglas)
|
||||
|
||||
@assert_no_queries
|
||||
def test_identity_preservation(self):
|
||||
"""Identity of test data is preserved between accesses."""
|
||||
self.assertIs(self.jim_douglas, self.jim_douglas)
|
||||
|
||||
@assert_no_queries
|
||||
def test_known_related_objects_identity_preservation(self):
|
||||
"""Known related objects identity is preserved."""
|
||||
self.assertIs(self.herbie.car, self.car)
|
||||
self.assertIs(self.herbie.belongs_to, self.jim_douglas)
|
||||
|
||||
@ignore_warnings(category=RemovedInDjango41Warning)
|
||||
def test_undeepcopyable(self):
|
||||
self.assertIs(self.non_deepcopy_able, self.__class__.non_deepcopy_able)
|
||||
|
||||
def test_undeepcopyable_warning(self):
|
||||
msg = (
|
||||
"Assigning objects which don't support copy.deepcopy() during "
|
||||
"setUpTestData() is deprecated. Either assign the "
|
||||
"non_deepcopy_able attribute during setUpClass() or setUp(), or "
|
||||
"add support for deepcopy() to "
|
||||
"test_utils.test_testcase.TestDataTests.non_deepcopy_able."
|
||||
)
|
||||
with self.assertRaisesMessage(RemovedInDjango41Warning, msg):
|
||||
self.non_deepcopy_able
|
||||
|
||||
def test_repr(self):
|
||||
self.assertEqual(
|
||||
repr(TestData('attr', 'value')),
|
||||
"<TestData: name='attr', data='value'>",
|
||||
)
|
||||
|
||||
|
||||
class SetupTestDataIsolationTests(TestCase):
|
||||
"""
|
||||
In-memory data isolation is respected for model instances assigned to class
|
||||
attributes during setUpTestData.
|
||||
"""
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.car = Car.objects.create(name='Volkswagen Beetle')
|
||||
|
||||
def test_book_name_deutsh(self):
|
||||
self.assertEqual(self.car.name, 'Volkswagen Beetle')
|
||||
self.car.name = 'VW sKäfer'
|
||||
self.car.save()
|
||||
|
||||
def test_book_name_french(self):
|
||||
self.assertEqual(self.car.name, 'Volkswagen Beetle')
|
||||
self.car.name = 'Volkswagen Coccinelle'
|
||||
self.car.save()
|
||||
|
||||
Reference in New Issue
Block a user