Django có sẵn 3 lớp test khác nhau ở cách xử lý DB và mức độ isolation.
TestCase wrap mỗi test trong transaction.atomic() rồi rollback khi xong — DB sạch giữa các test, rất nhanh, là lựa chọn mặc định. TransactionTestCase không wrap transaction nên mỗi test commit/rollback thật — chậm hơn, nhưng bắt buộc khi test logic dùng transaction.on_commit, select_for_update, hoặc nhiều DB connection. SimpleTestCase không cho phép DB access — chỉ test pure logic (template, util, form không save). Client là fake HTTP client để gọi view như request thật, không cần chạy server.
from django.test import TestCase, Client
from django.urls import reverse
class PostViewTest(TestCase):
@classmethod
def setUpTestData(cls): # chạy 1 lần cho cả class
cls.user = User.objects.create_user(email='a@b.com', password='x')
cls.post = Post.objects.create(title='Hi', author=cls.user)
def setUp(self): # chạy mỗi test
self.client = Client()
self.client.force_login(self.user)
def test_detail_page(self):
url = reverse('blog:post-detail', kwargs={'slug': self.post.slug})
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Hi')Project lớn hay dùng pytest-django thay vì stdlib test — fixture mạnh hơn, có @pytest.mark.django_db và client fixture sẵn.
Một bẫy nhỏ: setUpTestData chạy 1 lần per class (nhanh) nhưng object trả về shared giữa các test — test nào mutate object thì phải đổi sang setUp. Và signal post_save vẫn fire trong test; nếu signal gọi email/API thật thì mock bằng @override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend') hoặc unittest.mock.patch.