Dear developers,
Django 1.8 introduced the `TestCase.setUpTestData()` class method as a mean to
speed up test fixtures initialization as compared to using `setUp()`[0].
As I've come to use this feature and review changes from peers using it in
different projects the fact that test data assigned during its execution
couldn't be safely altered by test methods without compromising test isolation
has often be the source of confusion and frustration.
While the `setUpTestData` documentation mentions this limitation[1] and ways to
work around it by using `refresh_from_db()` in `setUp()` I believe it defeats
the whole purpose of the feature; avoiding unnecessary roundtrips to the
database to speed up execution. Given `TestCase` goes through great lengths to
ensure database level data isolation I believe it should do the same with class
level in-memory data assigned during `setUpTestData`.
In order to get rid of this caveat of the feature I'd like to propose an
adjustment to ensure such in-memory test data isolation.
What I suggest doing is wrapping all attributes assigned during `setUpTestData`
in descriptors that lazily return `copy.deepcopy()`ed values on instance
attribute accesses. By attaching the `deepcopy()`'s memo on test instances we
can ensure that the reference graph between objects is preserved and thus
backward compatible.
In other words, the following test would pass even if `self.book` is a deep
copy of `cls.book`.
class BookTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.author = Author.objects.create()
cls.book = cls.author.books.create()
def test_relationship_preserved(self):
self.assertIs(self.book.author, self.author)
Lazily returning `deepcopy'ies and caching returned values in `__dict__` à la
`cached_property` should also make sure the slight performance overhead this
incurs is minimized.
From a check against a few projects and Django's test suite[2] I have only
identified a single issue which is that attributes assigned during
`setUpTestData` would now have to be `deepcopy()`able but it shouldn't be
a blocker given `Model` instance are.
In order to allow other possible issues from being identified against existing
projects I packaged the proposed feature[3] and made it available on pypi[4]. It
requires decorating `setUpTestData` methods but it shouldn't be too hard to
apply to your projects if you want to give it a try.
Given this reaches consensus that this could be a great addition I'd file
a ticket and finalize what I have so far[2].
Thank your for your time,
Simon
[0]
https://docs.djangoproject.com/en/1.8/releases/1.8/#testcase-data-setup[1]
https://docs.djangoproject.com/en/2.1/topics/testing/tools/#django.test.TestCase.setUpTestData[2]
https://github.com/charettes/django/compare/setuptestdata...charettes:testdata[3]
https://github.com/charettes/django-testdata[4]
https://pypi.org/project/django-testdata/