View Django chạy đúng trên máy bạn chưa chắc đã chạy đúng trên production — chỉ một slug sai hay một trường form thiếu cũng đủ biến trang thành lỗi 500. Viết test bằng TestCase và Client có sẵn của Django giúp bạn gọi view như trình duyệt thật, kiểm tra status code, nội dung và template chỉ trong vài dòng, rồi chạy lại toàn bộ chỉ với một lệnh python manage.py test trước mỗi lần deploy.
Vấn đề
Nhiều dự án Django nhỏ không có test nào cho view. Quy trình kiểm tra duy nhất là mở trình duyệt, bấm qua vài trang, thấy chạy thì deploy. Cách này bỏ sót các trường hợp biên: slug không tồn tại, form thiếu trường, trang yêu cầu đăng nhập. Ví dụ view chi tiết bài viết dùng Post.objects.get() trực tiếp:
# views.pyfrom django.shortcuts import renderfrom .models import Postdef post_detail(request, slug): post = Post.objects.get(slug=slug) # slug sai -> DoesNotExist -> lỗi 500 return render(request, "blog/detail.html", {"post": post})
Người dùng chỉ cần gõ nhầm một ký tự trong URL là nhận ngay trang lỗi 500 thay vì 404, và bạn chỉ biết khi có người báo lại. Lỗi càng nằm sâu trong các trường hợp hiếm gặp thì càng tốn thời gian sửa.
Giải pháp
Django đã có sẵn django.test.TestCase và test Client — không cần cài thêm thư viện nào. TestCase tạo database test riêng và tự dọn sạch sau mỗi test; Client gửi request thật qua URL như trình duyệt, trả về response để bạn assert status code, nội dung và template. Viết test cho view trên như sau:
# tests.pyfrom django.test import TestCasefrom django.urls import reversefrom .models import Postclass PostDetailViewTests(TestCase): @classmethod def setUpTestData(cls): cls.post = Post.objects.create( title="Hello Django", slug="hello-django", content="Nội dung bài viết.", ) def test_detail_tra_200_va_hien_tieu_de(self): response = self.client.get( reverse("post-detail", kwargs={"slug": "hello-django"}) ) self.assertEqual(response.status_code, 200) self.assertContains(response, "Hello Django") self.assertTemplateUsed(response, "blog/detail.html") def test_slug_sai_tra_404(self): response = self.client.get( reverse("post-detail", kwargs={"slug": "khong-ton-tai"}) ) self.assertEqual(response.status_code, 404)
Chạy python manage.py test — test thứ hai sẽ FAIL ngay, chỉ ra view đang trả 500 thay vì 404. Sửa view bằng get_object_or_404 là xong:
# views.py — bản sửafrom django.shortcuts import get_object_or_404, renderfrom .models import Postdef post_detail(request, slug): post = get_object_or_404(Post, slug=slug) return render(request, "blog/detail.html", {"post": post})
Chạy lại test, cả hai đều pass. setUpTestData chạy một lần cho cả class nên test nhanh; mỗi test vẫn chạy trong transaction riêng nên dữ liệu không rò rỉ sang test khác. Khi view thay đổi, chạy lại toàn bộ test là biết ngay chỗ nào vỡ.
Áp dụng vào dự án của bạn
Bắt đầu từ ba view quan trọng nhất: trang chi tiết hay gặp lỗi nhất, form tạo và sửa dữ liệu, và trang yêu cầu đăng nhập. Mỗi view chỉ cần hai đến ba test: trường hợp đúng trả 200 và hiện nội dung, trường hợp sai trả 404 hoặc redirect, và trường hợp form lỗi trả lại đúng form kèm thông báo. Gắn python manage.py test vào bước cuối trước khi deploy — hoặc vào CI — để lỗi mới không bao giờ lọt ra production mà không ai hay.
Bài viết liên quan
- Django: bắt lỗi N+1 bằng assertNumQueries trong test — khỏi chờ production “nổ”
- Django: lấy object an toàn bằng get_object_or_404, khỏi viết try/except DoesNotExist trong 5 phút
- Django: viết view tạo và sửa bản ghi nhanh gấp đôi bằng CreateView và UpdateView trong 5 phút
Câu hỏi thường gặp
TestCase khác gì SimpleTestCase?
SimpleTestCase không cần database, dùng cho test không chạm DB như form hay template tag. TestCase tạo database test riêng và bọc mỗi test trong một transaction nên phù hợp khi view truy vấn model.
Client trong test khác gì requests?
self.client là test Client của Django: nó đi qua URLconf, middleware và view thật nhưng không cần chạy server, không cần mạng. Nó trả về response có sẵn status_code, content, context và templates để bạn assert trực tiếp.
Test có làm chậm deploy không?
Một bộ test view nhỏ thường chạy dưới 10 giây. Chạy python manage.py test trước khi deploy rẻ hơn nhiều so với việc sửa lỗi 500 trên production lúc nửa đêm.
Nên đặt test ở đâu?
Đặt trong tests.py của từng app, hoặc thư mục tests/ nếu app lớn. Django tự phát hiện file bắt đầu bằng test khi bạn chạy python manage.py test.
💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.
Nguồn tham khảo
- Django documentation — Testing tools: https://docs.djangoproject.com/en/6.1/topics/testing/tools/
- Django documentation — Writing and running tests: https://docs.djangoproject.com/en/6.1/topics/testing/overview/
