Django 앱을 만들면 자동으로 같이 생기는 tests.py.
대부분의 개발자는 한 번쯤 이런 생각을 해봅니다.

“이 파일… 지워도 되는 거 아닌가?”
“도대체 어떻게 쓰라는 거지?”

사실 tests.pyDjango가 공식적으로 권장하는 테스트 진입점이고,
조금만 익숙해지면 리팩토링, 기능 추가, 버전 업그레이드 때 진짜 큰 보험이 됩니다. (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 테스트는 특히 다음 상황에서 빛을 발합니다.

  1. 모델/비즈니스 로직 검증

    • 계산 로직, 상태 변경, 커스텀 메서드가 복잡할수록

    • 한 번 테스트 만들어 두면, 이후 수정 시에도 안심하고 리팩토링 가능

  2. 뷰/URL/권한 체크

    • 인증 여부에 따라 응답 코드가 달라지는지

    • 잘못된 입력에 대해 적절히 redirect / error 처리하는지

    • 템플릿에 필요한 context가 제대로 넘어가는지

  3. 회귀 버그 방지

    • 버그를 한 번 잡았으면, 테스트로 박제해 놓으면 같은 문제가 다시 안 생김
  4. 버전 업그레이드 시 안정성 확보

    • 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)
  • TestCaseunittest.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. “버그 재현용 테스트”로 회귀 막기

  1. 프로덕션에서 버그 발생

  2. 로컬에서 재현용 테스트 작성 (실패해야 정상)

  3. 코드 수정

  4. 테스트가 성공하는지 확인

  5. 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())
  • 폼의 유효성 검사 규칙을 문서처럼 테스트로 남길 수 있습니다.

test py를 작성하는 Django dev

8. 정리 – tests.py를 지우지 말고, 한 번만 제대로 써보자

한 줄 요약하면:

tests.py는 “Django 앱의 안전망”을 만드는 파일이다.

  • 처음엔 귀찮지만,

  • 한 번 테스트를 기반으로 개발하는 흐름을 맛보면

  • 리팩토링/기능추가/버전업 때 스트레스가 확 줄어듭니다.

실제 프로젝트에서:

  1. 새 기능을 만들 때 – 최소 1~2개의 테스트라도 같이 추가

  2. 버그를 고칠 때 – 반드시 그 버그를 재현하는 테스트부터 작성

  3. 프로젝트 키우기 시작할 때tests.pytests/ 패키지로 리팩토링

이 패턴만 지켜도 “테스트 없는 Django 프로젝트”와는 완전히 다른 개발 경험을 하게 됩니다.