Django 앱을 만들면 자동으로 같이 생기는 tests.py.
대부분의 개발자는 한 번쯤 이런 생각을 해봅니다.
“이 파일… 지워도 되는 거 아닌가?”
“도대체 어떻게 쓰라는 거지?”
사실 tests.py는 Django가 공식적으로 권장하는 테스트 진입점이고,
조금만 익숙해지면 리팩토링, 기능 추가, 버전 업그레이드 때 진짜 큰 보험이 됩니다. (Django Project)
이 글에서는:
-
tests.py가 무엇을 위한 파일인지 -
어떻게 작성하고 실행하는지
-
어떤 상황에서 특히 유용한지
-
실전 사용 예시
를 한 번에 정리합니다.
1. tests.py는 무슨 파일인가?
Django는 Python 표준 라이브러리의 unittest 기반 테스트 프레임워크를 사용합니다.
-
django.test.TestCase등을 상속한 테스트 클래스를 만들고 -
그 안에
test_로 시작하는 메서드를 작성하면 -
./manage.py test명령으로 자동으로 찾아 실행합니다.
startapp으로 앱을 만들면 기본으로 tests.py가 생성되는데:
-
작은 프로젝트 →
tests.py하나만 써도 충분 -
규모가 커지면 →
tests/패키지로 쪼개서(test_models.py,test_views.py등) 관리하는 것을 Django도 문서에서 권장합니다.
즉, “앱 단위 테스트를 위한 기본 위치” 라고 이해하면 됩니다.
2. 언제 유용한가? (왜 굳이 테스트를 써야 하나)
Django 테스트는 특히 다음 상황에서 빛을 발합니다.
-
모델/비즈니스 로직 검증
-
계산 로직, 상태 변경, 커스텀 메서드가 복잡할수록
-
한 번 테스트 만들어 두면, 이후 수정 시에도 안심하고 리팩토링 가능
-
-
뷰/URL/권한 체크
-
인증 여부에 따라 응답 코드가 달라지는지
-
잘못된 입력에 대해 적절히 redirect / error 처리하는지
-
템플릿에 필요한 context가 제대로 넘어가는지
-
-
회귀 버그 방지
- 버그를 한 번 잡았으면, 테스트로 박제해 놓으면 같은 문제가 다시 안 생김
-
버전 업그레이드 시 안정성 확보
-
Django 또는 라이브러리 버전을 올릴 때
-
./manage.py test한 번 돌려보고 시작할 수 있음
-
3. 가장 기본적인 tests.py 예제
3-1. 모델 테스트
# myapp/tests.py
from django.test import TestCase
from .models import Article
class ArticleModelTests(TestCase):
def setUp(self):
self.article = Article.objects.create(
title="테스트 제목",
content="본문",
views=0,
)
def test_increase_views(self):
# given
self.assertEqual(self.article.views, 0)
# when
self.article.increase_views()
# then
self.assertEqual(self.article.views, 1)
-
TestCase는unittest.TestCase를 확장한 Django용 클래스입니다. -
각 테스트는 트랜잭션 단위로 격리되며, 테스트마다 DB가 롤백되어 서로 간섭하지 않습니다.
4. 뷰/URL을 테스트하는 방법 – Client 사용
Django는 테스트 전용 HTTP 클라이언트 django.test.Client를 제공합니다.
실제 서버를 띄우지 않고도 URL에 GET/POST 요청을 보내고 응답을 검사할 수 있습니다.
4-1. 간단한 뷰 테스트
# myapp/tests.py
from django.test import TestCase
from django.urls import reverse
from .models import Article
class ArticleViewTests(TestCase):
def setUp(self):
self.article = Article.objects.create(
title="게시글 1",
content="내용",
views=0,
)
def test_article_detail_page_returns_200(self):
url = reverse("article_detail", args=[self.article.id])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "게시글 1")
여기서 핵심은:
-
self.client:TestCase에 기본 제공되는 테스트 클라이언트 인스턴스 -
reverse("article_detail", args=[...]): URL 하드코딩 대신 URL name으로 조회 -
assertContains: 응답 본문에 특정 문자열이 포함됐는지 검사
4-2. 로그인/권한 체크 테스트
from django.contrib.auth import get_user_model
class ArticlePermissionTests(TestCase):
def setUp(self):
self.user = get_user_model().objects.create_user(
username="tester",
password="pass1234",
)
self.article = Article.objects.create(
title="secret",
content="비밀글",
views=0,
)
def test_anonymous_user_redirected_to_login(self):
url = reverse("article_edit", args=[self.article.id])
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
self.assertIn("/accounts/login", response["Location"])
def test_logged_in_user_can_access_edit_page(self):
self.client.login(username="tester", password="pass1234")
url = reverse("article_edit", args=[self.article.id])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
이런 식으로:
-
익명 사용자 접근 시 redirect 되는지
-
로그인한 사용자는 정상 접근 가능한지
권한 관련 버그를 초기에 걸러낼 수 있습니다.
5. tests.py를 언제 / 어떻게 실행하나?
5-1. 전체 테스트 실행
$ python manage.py test
- 현재 디렉토리 이하의
test*.py파일에서
unittest.TestCase또는django.test.TestCase서브클래스를 자동으로 찾아 실행합니다.
5-2. 특정 앱만 실행
$ python manage.py test myapp
5-3. 특정 모듈 / 클래스 / 메서드만 실행
# myapp.tests 모듈 전체
$ python manage.py test myapp.tests
# 특정 TestCase
$ python manage.py test myapp.tests.ArticleViewTests
# 특정 테스트 메서드
$ python manage.py test myapp.tests.ArticleViewTests.test_article_detail_page_returns_200
개발 중에는 수정한 부분만 빠르게 돌려보는 습관을 들이면 좋습니다.
6. tests.py를 tests 패키지로 확장하기
프로젝트가 커지면 tests.py 하나로는 감당이 안 됩니다.
Django 문서에서도 패키지 구조로 쪼개는 것을 권장합니다.(Django Project)
예:
myapp/
tests/
__init__.py
test_models.py
test_views.py
test_forms.py
test_api.py
각 파일 안에는:
# myapp/tests/test_models.py
from django.test import TestCase
from myapp.models import Article
class ArticleModelTests(TestCase):
...
이렇게 나눠도 실행은 동일합니다.
$ python manage.py test myapp # 전체
$ python manage.py test myapp.tests.test_models # 모델 테스트만
7. 실전 사용 사례 몇 가지
7-1. “버그 재현용 테스트”로 회귀 막기
-
프로덕션에서 버그 발생
-
로컬에서 재현용 테스트 작성 (실패해야 정상)
-
코드 수정
-
테스트가 성공하는지 확인
-
git에 테스트 + 수정 코드 같이 커밋
이렇게 해두면 나중에 리팩토링할 때 같은 버그가 다시 나지 않습니다.
7-2. API 응답 포맷 고정
프론트엔드와 협업 시, API 응답 형식이 아주 중요합니다.
class ArticleApiTests(TestCase):
def setUp(self):
self.article = Article.objects.create(
title="API 제목",
content="내용",
views=0,
)
def test_article_detail_api_response(self):
url = reverse("api:article-detail", args=[self.article.id])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertEqual(data["id"], self.article.id)
self.assertIn("title", data)
self.assertIn("content", data)
-
응답에 어떤 필드가 반드시 포함되어야 하는지 명시
-
필드명을 실수로 바꾸거나 제거하면 테스트에서 바로 실패
7-3. 폼 검증 로직 테스트
from .forms import ArticleForm
class ArticleFormTests(TestCase):
def test_title_is_required(self):
form = ArticleForm(data={"title": "", "content": "내용"})
self.assertFalse(form.is_valid())
self.assertIn("title", form.errors)
def test_valid_form(self):
form = ArticleForm(data={"title": "제목", "content": "내용"})
self.assertTrue(form.is_valid())
- 폼의 유효성 검사 규칙을 문서처럼 테스트로 남길 수 있습니다.

8. 정리 – tests.py를 지우지 말고, 한 번만 제대로 써보자
한 줄 요약하면:
tests.py는 “Django 앱의 안전망”을 만드는 파일이다.
-
처음엔 귀찮지만,
-
한 번 테스트를 기반으로 개발하는 흐름을 맛보면
-
리팩토링/기능추가/버전업 때 스트레스가 확 줄어듭니다.
실제 프로젝트에서:
-
새 기능을 만들 때 – 최소 1~2개의 테스트라도 같이 추가
-
버그를 고칠 때 – 반드시 그 버그를 재현하는 테스트부터 작성
-
프로젝트 키우기 시작할 때 –
tests.py→tests/패키지로 리팩토링
이 패턴만 지켜도 “테스트 없는 Django 프로젝트”와는 완전히 다른 개발 경험을 하게 됩니다.
댓글이 없습니다.