일반적으로 DRF로 줄여 부르는 Django REST Framework는 Django 기반으로 API를 구축하기 위한 표준 툴킷입니다. DRF는 직렬 변환기(serializers), 뷰셋(viewsets), 라우터(routers) 및 인증 레이어(authentication layer)를 제공합니다. 또한 많은 개발자들이 제대로 활용하지 못하는, Django 자체 테스트 러너를 기반으로 하는 견고한 테스트 레이어도 제공합니다.
이 가이드에서는 DRF API를 두 가지 방법으로 테스트하는 방법을 보여줍니다. 첫째, DRF의 `APITestCase`와 `APIClient`를 사용하여 Python으로 작성된 자동화된 테스트로, 라이브 서버 없이 실행되며 모든 커밋에서 회귀(regressions)를 파악합니다. 둘째, API 클라이언트를 사용하여 실행 중인 엔드포인트를 테스트하는 것으로, 이는 동작을 탐색하고 실제 서비스를 검증하는 방법입니다. 둘 다 중요하며, 서로 다른 문제를 파악합니다.
프로젝트 및 테스트 환경 설정
테스트 종속성이 시스템의 나머지 부분과 분리되도록 격리된 환경에서 시작하십시오:
python -m venv venv
source venv/bin/activate
pip install django djangorestframework coverage
DRF 프로젝트는 다른 Django 프로젝트와 마찬가지로 테스트됩니다. 관례적으로, 테스트는 각 앱 내의 `tests.py` 파일에 있거나, 테스트가 많을 경우 `tests/` 패키지에 있습니다. Django의 테스트 러너는 `TestCase` 변형을 상속하는 모든 클래스와 `test_`로 시작하는 모든 메서드를 찾습니다.
핵심 선택은 사용할 기본 클래스입니다. 일반 `unittest.TestCase`는 데이터베이스가 없습니다. Django의 `TestCase`는 각 테스트를 트랜잭션으로 래핑하고 롤백하여 테스트가 서로 오염되지 않도록 합니다. DRF의 `APITestCase`는 Django의 `TestCase`를 확장하고 DRF 인증 및 콘텐츠 유형을 이해하는 DRF의 `APIClient`를 사용합니다. API 작업에는 `APITestCase`를 사용하십시오.
한 가지 더 알아두어야 할 클래스는 `TransactionTestCase`입니다. 이 클래스는 테스트 코드 자체가 트랜잭션을 관리하거나 래핑 트랜잭션이 숨길 수 있는 데이터베이스 기능에 의존하는 경우에 필요한 트랜잭션으로 테스트를 래핑하지 않습니다. 롤백하는 대신 테스트 사이에 테이블을 자르기 때문에 더 느리므로, `TestCase`가 동작을 제대로 모델링할 수 없을 때만 사용하십시오. 대부분의 엔드포인트 테스트에서는 `APITestCase`가 적절하고 가장 빠른 선택입니다.
또한 테스트 데이터를 조기에 생각하는 것이 좋습니다. 클래스의 각 테스트에 필요한 행을 생성하기 위해 `setUp` 메서드를 사용하거나, 데이터가 읽기 전용이고 속도를 위해 클래스 전체에서 공유될 수 있는 경우 `setUpTestData`를 사용하십시오. 대규모 프로젝트의 경우, `factory_boy`와 같은 팩토리 라이브러리는 모든 필드를 명시하지 않고도 유효한 모델 인스턴스를 생성하여 모델에 새 필수 필드가 추가될 때 테스트를 짧고 견고하게 유지합니다.
직렬 변환기(Serializers)를 단위로 테스트하기
직렬 변환기는 유효성 검사를 수행하고 모델 인스턴스와 JSON 간을 변환합니다. 작고 순수하므로 엔드포인트를 건드리지 않는 빠른 단위 테스트에 이상적입니다.
`Article` 모델과 `ArticleSerializer`가 있다고 가정해 봅시다. 단위 테스트는 유효한 데이터는 통과하고 유효하지 않은 데이터는 실패하는지 확인합니다:
from django.test import TestCase
from articles.serializers import ArticleSerializer
class ArticleSerializerTests(TestCase):
def test_valid_data_passes(self):
data = {"title": "Caching strategies", "body": "Use ETags."}
serializer = ArticleSerializer(data=data)
self.assertTrue(serializer.is_valid())
def test_missing_title_fails(self):
data = {"body": "No title here."}
serializer = ArticleSerializer(data=data)
self.assertFalse(serializer.is_valid())
self.assertIn("title", serializer.errors)
이 테스트는 HTTP나 뷰가 없기 때문에 밀리초 단위로 실행됩니다. 엔드포인트를 고려하기 전에도 유효성 검사 규칙이 올바른지 알려줍니다. 이 단위 수준 커버리지는 자동화된 테스트란 무엇인가에 설명된 테스트 피라미드의 기본입니다.
APITestCase 및 APIClient로 엔드포인트 테스트하기
엔드포인트 테스트는 전체 경로(라우팅, 뷰, 직렬 변환기, 응답)를 확인합니다. DRF의 `APIClient`는 프로세스 내에서 요청을 보내므로 서버가 실행되지 않고 테스트가 빠르게 유지됩니다.
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from articles.models import Article
class ArticleEndpointTests(APITestCase):
def setUp(self):
Article.objects.create(title="First post", body="Hello world")
def test_list_articles_returns_200(self):
url = reverse("article-list")
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
def test_create_article(self):
url = reverse("article-list")
payload = {"title": "Second post", "body": "More content"}
response = self.client.post(url, payload, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Article.objects.count(), 2)
몇 가지 주목할 만한 세부 사항이 있습니다. `self.client`는 `APITestCase`에서 제공하는 `APIClient` 인스턴스입니다. 하드코딩된 URL 대신 경로 이름을 사용하여 `reverse()`를 사용하면 경로 변경으로 인해 모든 테스트가 중단되지 않습니다. 클라이언트가 페이로드를 올바르게 직렬화하도록 쓰기 작업 시 `format="json"`을 전달하십시오. `rest_framework.status`의 명명된 상수로 상태 코드를 단언하십시오. 이는 원시 숫자보다 더 명확하게 읽힙니다. 상태 코드 자체는 REST API가 사용해야 할 HTTP 상태 코드 가이드에 설명되어 있습니다.
인증 및 권한 테스트하기
대부분의 실제 API는 엔드포인트를 보호합니다. 인증되지 않은 요청은 거부되고 올바른 권한이 있는 인증된 요청은 성공한다는 것을 증명하는 테스트가 필요합니다.
`APIClient`는 두 가지 방법으로 테스트를 인증할 수 있습니다. `force_authenticate()`는 자격 증명 확인을 건너뛰고 사용자를 직접 연결하며, 이는 뷰 로직 자체를 테스트하는 데 이상적입니다. `login()` 또는 `credentials()`는 실제 인증 경로를 실행합니다. 다음은 두 가지 방향을 모두 사용하는 권한 테스트입니다:
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase
from django.urls import reverse
class ArticlePermissionTests(APITestCase):
def setUp(self):
self.user = User.objects.create_user(
username="editor", password="testpass123"
)
self.url = reverse("article-list")
def test_anonymous_cannot_create(self):
payload = {"title": "Blocked", "body": "Should fail"}
response = self.client.post(self.url, payload, format="json")
self.assertEqual(
response.status_code, status.HTTP_403_FORBIDDEN
)
def test_authenticated_user_can_create(self):
self.client.force_authenticate(user=self.user)
payload = {"title": "Allowed", "body": "Should pass"}
response = self.client.post(self.url, payload, format="json")
self.assertEqual(
response.status_code, status.HTTP_201_CREATED
)
첫 번째 테스트는 권한 클래스가 익명 쓰기를 차단하는지 확인합니다. 두 번째 테스트는 인증된 사용자가 통과하는지 확인합니다. 이 두 테스트는 리팩토링 중에 조용히 깨질 수 있는 권한 경계를 정확히 파악합니다. 토큰 인증을 특별히 테스트하는 경우, `force_authenticate`를 `self.client.credentials(HTTP_AUTHORIZATION="Token " + token)`으로 바꾸어 실제 헤더 경로를 테스트하십시오.
어떤 것을 사용할지에 대해 의도적으로 생각하는 것이 중요합니다. `force_authenticate`는 더 빠르고 뷰 로직을 격리하므로, 특정 역할이 무엇인가를 할 수 있는지 없는지에만 관심 있는 대부분의 권한 테스트에 적합합니다. 실제 자격 증명 경로는 인증 메커니즘이 작동하는지 특별히 증명하는 소규모 테스트 세트에 필요합니다. 예를 들어, 잘못된 토큰이 거부되는지, 만료된 토큰이 실패하는지, 로그인 엔드포인트가 사용 가능한 토큰을 발급하는지 등입니다. 둘을 혼합하면 저렴하게 광범위한 커버리지를 제공하고 중요한 부분에 대해 깊이 있는 커버리지를 제공합니다.
객체 수준 권한을 잊지 마십시오. 많은 DRF API는 사용자가 자신의 기록을 편집할 수 있지만 다른 사람의 기록은 편집할 수 없도록 합니다. 이를 명시적으로 테스트하십시오. 두 명의 사용자를 만들고, 한 명이 기록을 생성하게 한 다음, 다른 사용자가 해당 기록을 수정하려고 할 때 `403` 또는 `404`를 받는지 확인하십시오. 목록 엔드포인트도 동일한 정밀 조사가 필요합니다. 세부 엔드포인트가 잠겨 있더라도 누수된 쿼리셋은 사용자가 볼 수 없는 행을 반환할 수 있기 때문입니다.
테스트 스위트 실행 및 커버리지 측정
Django의 러너로 모든 테스트를 실행하십시오:
python manage.py test
러너는 테스트 클래스를 찾고, 임시 테스트 데이터베이스를 설정하고, 각 테스트를 트랜잭션 내에서 실행한 다음 롤백합니다. 클린 실행은 `OK`를 출력합니다. 실패는 깨진 단언문과 위치를 보여줍니다.
스위트가 통과했다는 것을 아는 것이 충분히 커버된다는 것을 아는 것과 같지는 않습니다. `coverage` 도구는 실제로 실행된 줄을 측정합니다:
coverage run --source='.' manage.py test
coverage report
coverage html
보고서는 각 파일과 실행된 줄의 백분율을 나열합니다. HTML 출력은 테스트되지 않은 줄을 빨간색으로 강조 표시하여 간격을 직접 알려줍니다. 단일 헤드라인 숫자보다는 뷰, 직렬 변환기 및 권한에 대한 의미 있는 커버리지를 목표로 하십시오. 자동화된 테스트 스크립트를 작성하는 방법에 대한 글은 약한 테스트의 커버리지가 실제 안전이 아니기 때문에 테스트를 유지할 가치가 있는 요소에 대해 설명합니다.
클라이언트로 라이브 API 테스트하기
자동화된 Python 테스트는 빠르고 CI에서 실행되지만, 앱을 프로세스 내에서 테스트합니다. 이들은 실행 중인 서비스에서만 나타나는 문제(예: 잘못 구성된 CORS 헤더, 무언가를 제거하는 리버스 프록시, 실제 부하에서 느린 데이터베이스 또는 테스트 데이터베이스와 프로덕션 간의 차이)를 파악하지 못합니다. 이를 위해서는 배포된 엔드포인트에 실제 요청을 보내야 합니다.
Apidog는 이 경우에 아주 적합합니다. 올인원 API 플랫폼이므로, DRF API의 OpenAPI 스키마를 가져오고, 실행 중인 서버에 실시간 요청을 보내고, 추가 Python 코드 작성 없이 시각적으로 단언문을 구축할 수 있습니다. DRF는 OpenAPI 스키마를 생성할 수 있으며, Apidog는 이를 직접 소비하여 클라이언트를 실제 계약과 동기화 상태로 유지합니다. 또한 여러 단계의 테스트 시나리오를 구축할 수도 있습니다(예: 로그인, 게시물 생성, 가져오기, 삭제). 이를 일정에 따라 또는 CI에서 실행할 수 있습니다. 이는 `APITestCase` 스위트를 대체하기보다는 보완하는 역할을 합니다. 단위 및 엔드포인트 테스트는 코드를 보호하고, API 클라이언트는 배포된 서비스를 보호합니다. DRF 스키마를 가져와 시도하려면 Apidog를 다운로드할 수 있습니다. Python으로만 작업하는 것을 선호하는 팀을 위해 pytest API 자동화 테스트 프레임워크 가이드에서는 pytest에서 DRF 스타일 테스트를 실행하는 방법을 보여줍니다.
자주 묻는 질문
TestCase와 APITestCase의 차이점은 무엇인가요?
Django의 `TestCase`는 각 테스트를 데이터베이스 트랜잭션으로 래핑하고 표준 Django 테스트 클라이언트를 제공합니다. DRF의 `APITestCase`는 이를 서브클래싱하고 클라이언트를 `APIClient`로 교체합니다. `APIClient`는 DRF 인증 체계, 콘텐츠 협상, 요청의 `format` 인수를 이해합니다. DRF 엔드포인트를 테스트하려면 `APITestCase`를 사용하십시오.
login 대신 force_authenticate를 언제 사용해야 하나요?
실제 자격 증명 흐름의 비용과 복잡성 없이 뷰 및 권한 로직을 테스트하려면 `force_authenticate()`를 사용하십시오. 이는 사용자(user)를 요청에 직접 연결합니다. 세션 또는 토큰 인증과 같은 인증 메커니즘 자체를 확인하려는 경우에는 `login()` 또는 `credentials()`를 사용하십시오.
DRF 테스트에 실행 중인 서버가 필요한가요?
아니요. `APIClient`는 요청을 프로세스 내에서 뷰로 직접 전달하므로 테스트 스위트는 서버를 시작하지 않고 실행됩니다. 이것이 빠른 이유입니다. 프록시 및 CORS와 같은 인프라를 포함하여 실제로 배포된 서비스를 테스트하려면 Apidog와 같은 API 클라이언트를 사용하여 실제 HTTP 요청을 보냅니다.
DRF 프로젝트의 테스트 커버리지를 어떻게 확인하나요?
`coverage` 패키지를 설치한 다음 `coverage run --source='.' manage.py test`를 실행하고, 요약을 보려면 `coverage report`를, 줄 단위 보기를 보려면 `coverage html`을 실행하십시오. HTML 보고서는 테스트되지 않은 줄을 강조 표시하여 어떤 뷰나 직렬 변환기에 테스트가 없는지 정확히 확인할 수 있습니다.
직렬 변환기(Serializers)와 엔드포인트는 따로 테스트해야 하나요?
네. 직렬 변환기 단위 테스트는 빠르고 HTTP 오버헤드 없이 유효성 검사 버그를 정확히 찾아냅니다. `APITestCase`를 사용한 엔드포인트 테스트는 라우팅, 권한 및 전체 요청 주기를 확인합니다. 둘 다 유지하면 단위 수준에서 빠른 피드백을 얻을 수 있고, 통합 수준에서 모든 연결이 제대로 작동한다는 확신을 가질 수 있습니다.
