본문 바로가기
Django/Django REST framework

13-Testing

by hyun-am 2021. 8. 24.
반응형

테스트가 없는 코드는 잘못 설계된 코드입니다. - Jacob Kaplan-Moss

REST 프레임워크에는 Django의 기존 테스트 프레임워크를 확장하고 API요청 지원을 개선하는 몇 가지 support 클래스가 포함되어 있습니다.

APIRequestFactory

Django의 RequestFactory 클래스를 확장합니다.

Creating test requests

APIRequestFactory 클래스는 Django의 표준 RequestFactory 클래스와 거의 동일한 API를 지원합니다. 이는 표준 .get(), .post(), .put(), .patch(), .delete(), .head() 및 .options()메서드를 모두 사용할 수 있음을 의미합니다.

 

from rest_framework.test import APIRequestFactory

# Using the standard RequestFactory API to create a form POST request
factory = APIRequestFactory()
request = factory.post('/notes/', {'title': 'new idea'})

Using the format argument

post, put 및 patch와 같은 요청 본문을 생성하는 메소드에는 multipart 양식 데이터 이외의 콘텐츠 유형을 사용하여 요청을 쉽게 생성할 수 있도록 하는 format 아규먼트가 포함됩니다. 예를 들면 다음과 같습니다.

 

# Create a JSON POST request
factory = APIRequestFactory()
request = factory.post('/notes/', {'title': 'new idea'}, format='json')

기본적으로 사용 가능한 형식은 'multipart' 및 'json'입니다. Django의 기존 RequestFactory와의 호환성을 위해 기본 형식은 'multipart'입니다.

더 광범위한 요청 형식을 지원하거나 기본 형식을 변경하려면 구성 섹션을 참조하면 되겠습니다.

링크 : https://www.django-rest-framework.org/api-guide/testing/#configuration

 

Testing - Django REST framework

 

www.django-rest-framework.org

Explicitly encoding the request body

request body을 명시적으로 인코딩해야 하는 경우 content_type 플래그를 설정하여 인코딩할 수 있습니다. 예를 들면 다음과 같습니다.

request = factory.post('/notes/', json.dumps({'title': 'new idea'}), content_type='application/json')

PUT and PATCH with form data

Django의 RequestFactory와 REST 프레임워크의 APIRequestFactory 사이에 주목할 가치가 있는 한 가지 차이점은 multipart form data가 .post() 이외의 메소드에 대해 인코딩된다는 것입니다.

예를 들어 APIRequestFactory를 사용하여 다음과 같은 PUT 요청 양식을 만들 수 있습니다.

factory = APIRequestFactory()
request = factory.put('/notes/547/', {'title': 'remember to email dave'})

Django의 RequestFactory를 사용하면 직접 데이터를 명시적으로 인코딩해야합니다.

from django.test.client import encode_multipart, RequestFactory

factory = RequestFactory()
data = {'title': 'remember to email dave'}
content = encode_multipart('BoUnDaRyStRiNg', data)
content_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg'
request = factory.put('/notes/547/', content, content_type=content_type)

Forcing authentication

요청 팩토리를 사용하여 직접 view를 테스트할 때 올바른 인증 자격 증명을 구성하는 것보다 요청을 직접 인증할 수 있는 것이 편리한 경우가 많습니다.

요청을 강제로 인증하려면 force_authenticate()메소드를 사용하면 됩니다.

from rest_framework.test import force_authenticate

factory = APIRequestFactory()
user = User.objects.get(username='olivia')
view = AccountDetail.as_view()

# Make an authenticated request to the view...
request = factory.get('/accounts/django-superstars/')
force_authenticate(request, user=user)
response = view(request)

메서드의 signature는 force_authenticate(request,user=None,token=None)입니다. 호출할 때 사용자와 토큰 중 하나 또는 둘 모두가 설정될 수 있습니다.

예를 들어 토큰을 사용하여 강제로 인증하는 경우 다음과 같이 사용할 수 있습니다.

user = User.objects.get(username='olivia')
request = factory.get('/accounts/django-superstars/')
force_authenticate(request, user=user, token=user.auth_token)

참고 : force_authenticate는 request.user를 메모리 내 사용자 인스턴스로 직접 설정합니다. 저장된 사용자 상태를 업데이트하는 여러 테스트에서 동일한 사용자 인스턴스를 재사용하는 경우 테스트 사이에서 동일한 사용자 인스턴스를 재사용하는 경우 테스트 사이에 refresh_from_db()를 호출해야 할 수 있습니다.

 

참고 : APIRequestFactory를 사용할 때 반환되는 객체는 뷰가 호출된 후에만 생성되는 REST 프레임워크의 Request 객체가 아니라 Django의 표준 HttpRequest입니다.

이것은 요청 객체에 직접 속성을 설정하는 것이 항상 기대하는 효과를 갖지 않을 수 있음을 의미합니다. 예를 들어, .token을 직접 설정하면 효과가 없으며 .user를 직접 설정하면 세션 인증이 사용되는 경우에만 작동합니다.

# Request will only authenticate if `SessionAuthentication` is in use.
request = factory.get('/accounts/django-superstars/')
request.user = user
response = view(request)

Forcing CSRF validation

기본적으로 APIRequestFactory로 생성된 요청은 DRF view에 전달될 때 CSRF 유효성 검사를 명시적으로 켜야 하는 경우 팩토리를 인스턴스화할 때 force_csrf_checks 플래그를 설정하여 그렇게 할 수 있습니다.

factory = APIRequestFactory(enforce_csrf_checks=True)

참고 : Django의 표준 RequestFactory는 이 옵션을 포함할 필요가 없습니다. 왜냐하면 일반 Django를 사용할 때 CSRF 유효성 검사가 미들웨어에서 일아니기 때문에 view를 직접 테스트할 때는 실행되지 않기 때문입니다. REST 프레임워크를 사용할 때 CSRF 유효성 검사는 view 내에서 발생하므로 reqeust factory는 view 수준 CSRF 검사를 비활성화해야 합니다.

 

APIClient

Django의 기존 클라이언트 클래스를 확장합니다. 예시 링크는 다음과 같습니다.

링크 : https://docs.djangoproject.com/en/stable/topics/testing/tools/#the-test-client

Making requests

APIClient 클래스는 Django의 표준 클라이언트 클래스와 request interface를 지원합니다. 이는 표준 .get(), .post(), .put(), .patch(), .delete(), .head() 및 .options() 메서드를 모두 사용할 수 있음을 의미합니다. 예를 들어 :

from rest_framework.test import APIClient

client = APIClient()
client.post('/notes/', {'title': 'new idea'}, format='json')

더 광범위한 요청 형식을 지원하거나 기본 형식을 변경하려면 구성 섹션을 참조하세요.

Authenticating

.login(**kwargs)

로그인 방법은 Django의 일반 클라이언트 클래스와 동일하게 작동합니다. 이를 통해 SessionAuthentication을 포함하는 모든 view에 대해 요청을 인증할 수 있습니다.

 

# Make all requests in the context of a logged in session.
client = APIClient()
client.login(username='lauren', password='secret')

로그아웃하려면 평소와 같이 logout메소드를 호출하면 되겠습니다.

# Log out
client.logout()

로그인 방법은 API와 AJAX 상호 작용을 포함하는 웹 사이트와 같이 세션 인증을 사용하는 API를 테스트하는 데 적합합니다.

.credentials(**kwargs)

자격 증명 방법을 사용하여 테스트 클라이언트의 모든 후속 요청에 포함될 헤더를 설정할 수 있습니다.

from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient

# Include an appropriate `Authorization:` header on all requests.
token = Token.objects.get(user__username='lauren')
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)

자격 증명을 두 번째로 호출하면 기존 자격 증명을 덮어씁니다. 아규먼트 없이 메서드를 호출하여 기존 자격 증명을 설정 해제할 수 있습니다.

# Stop including any credentials
client.credentials()

자격 증명 방법은 기본 인증, OAuth1a 및 OAuth2 인증, 간단한 토큰 인증 체계와 같은 인증 헤더가 필요한 API를 테스트하는 데 적합합니다.

.force_authenticate(user=None, token=None)

때로는 인증을 완전히 우회하고 테스트 클라이언트의 모든 요청이 자동으로 인증된 것으로 처리되도록 할 수 있습니다.

API를 테스트하고 있지만 테스트 요청을 하기 위해 유효한 인증 자격 증명을 구성할 필요가 없는 경우 유용한 바로 가기가 될 수 있습니다.

user = User.objects.get(username='lauren')
client = APIClient()
client.force_authenticate(user=user)

후속 요청의 인증을 취소하려면 force_authenticate를 호출하여 사용자 및/또는 토큰을 None으로 설정합니다.

client.force_authenticate(user=None)

CSRF validation

APIClient를 사용할 때는 기본적으로 CSRF validation이 적용되지 않습니다. CSRF 유효성 검사를 명시적으로 활성화해야 하는 경우 클라이언트를 인스턴스화할 때 force_csrf_checks 플래그를 설정하여 수행할 수 있습니다.

client = APIClient(enforce_csrf_checks=True)

평소와 같이 CSRF 유효성 검사는 세션 인증 view에만 적용됩니다. 이는 클라이언트가 login()을 호출하여 로그인한 경우에만 CSRF 유효성 검사가 발생함을 의미합니다.

RequestsClient

DRF에는 인기 있는 Python 라이브러리인 요청을 사용하여 애플리케이션과 상호 작용하기 위한 클라이언트도 포함되어 있습니다. 다음과 같은 경우에 유용할 수 있습니다.

  • 주로 다른 Python 서비스에서 API와 인터페이스할 것으로 예상하고 클라이언트가 보게 되는 것과 동일한 수준에서 서비스를 테스트하려고 합니다.
  • 스테이징 또는 라이브 환경에서도 실행할 수 있는 방식으로 테스트를 작성하려고 합니다. (아래 "실시간 테스트"를 참조하십시오)

이렇게 하면 요청 세션을 직접 사용하는 것과 똑같은 인터페이스가 노출됩니다.

from rest_framework.test import RequestsClient

client = RequestsClient()
response = client.get('http://testserver/users/')
assert response.status_code == 200

요청 클라이언트에서는 정규화된 URL을 전달해야합니다.

RequestsClient and working with the database

RequestsClient 클래스는 서비스 인터페이스와만 상호 작용하는 테스트를 작성하려는 경우에 유용합니다. 이것은 모든 상호 작용이 API를 통해 이루어져야 함을 의미하므로 표준 Django 테스트 클라이언트를 사용하는 것보다 약간 더 엄격합니다.

RequestsClient를 사용하는 경우 테스트 설정 및 결과 assertions이 데이터베이스 모델과 직접 상호 작용하는 대신 일반 API호출로 수행되는지 확인하고 싶을 것입니다. 예를 들어 Customer.objects.count() == 3인지 확인하는 대신 고객 엔드포인트를 나열하고 세 개의 레코드가 포함되어 있는지 확인합니다.

Headers & Authentication

사용자 정의 헤더 및 인증 자격 증명은 표준 requests.Session 인스턴스를 사용할 때와 동일한 방식으로 제공될 수 있습니다.

from requests.auth import HTTPBasicAuth

client.auth = HTTPBasicAuth('user', 'pass')
client.headers.update({'x-test': 'true'})

CSRF

SessionAuthentication을 사용하는 경우 POST,PUT,PATCH 또는 DELETE 요청에 대해 CSRF 토큰을 포함해야 합니다.

JavaScript 기반 클라이언트가 사용하는 것과 동일한 흐름을 따르면 그렇게 할 수 있습니다. 먼저 CSRF 토큰을 얻기 위해 GET요청을 하고 다음 요청에서 해당 토큰을 제시하면 되겠습니다.

예를들면 다음과 같습니다.

client = RequestsClient()

# Obtain a CSRF token.
response = client.get('http://testserver/homepage/')
assert response.status_code == 200
csrftoken = response.cookies['csrftoken']

# Interact with the API.
response = client.post('http://testserver/organisations/', json={
    'name': 'MegaCorp',
    'status': 'active'
}, headers={'X-CSRFToken': csrftoken})
assert response.status_code == 200

Live tests

신중하게 사용하면 RequestsClient와 CoreAPIClient 모두 개발 중에 실행하거나 스테이징 서버 또는 프로덕션 환경에 대해 직접 실행할 수 있는 테스트 사례를 작성할 수 있는 기능을 제공합니다.

이 스타일을 사용하여 몇 가지 핵심 기능의 기본 테스트를 만드는 것은 라이브 서비스를 검증하는 강력한 방법입니다. 그렇게 하려면 테스트가 고객 데이터에 직접적인 영향을 미치지 않는 방식으로 실행되도록 설정 및 분해에 주의를 기울여야 할 수 있습니다.

CoreAPIClient

CoreAPIClient를 사용하면 Python coreapi 클라이언트 라이브러리를 사용하여 API와 상호작용할 수 있습니다.

# Fetch the API schema
client = CoreAPIClient()
schema = client.get('http://testserver/schema/')

# Create a new organisation
params = {'name': 'MegaCorp', 'status': 'active'}
client.action(schema, ['organisations', 'create'], params)

# Ensure that the organisation exists in the listing
data = client.action(schema, ['organisations', 'list'])
assert(len(data) == 1)
assert(data == [{'name': 'MegaCorp', 'status': 'active'}])

Headers & Authentication

사용자 지정 헤더 및 인증은 RequestClient와 유사한 방식으로 CoreAPIClient와 함께 사용할 수 있습니다.

from requests.auth import HTTPBasicAuth

client = CoreAPIClient()
client.session.auth = HTTPBasicAuth('user', 'pass')
client.session.headers.update({'x-test': 'true'})

API Test cases

DRF에는 기존 Django 테스트 케이스 클래스를 미러링하지만 Django의 기본 클라이언트 대신 APIClient를 사용하는 다음 테스트 케이스 클래스가 포함됩니다.

  • APISimpleTestCase
  • APITransactionTestCase
  • APITestCase
  • APILiveServerTestCase

Example

일반 Django 테스트 케이스 클래스와 마찬가지로 DRF 테스트 케이스 클래스를 사용할 수 있습니다. self.client 속성은 APIClient 인스턴스가 됩니다.

from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from myproject.apps.core.models import Account

class AccountTests(APITestCase):
    def test_create_account(self):
        """
        Ensure we can create a new account object.
        """
        url = reverse('account-list')
        data = {'name': 'DabApps'}
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(Account.objects.count(), 1)
        self.assertEqual(Account.objects.get().name, 'DabApps')

URLPatternsTestCase

DRF 프레임워크는 또한 클래스별로 urlpattern을 분리하기 위한 테스트 케이스 클래스를 제공합니다. 이것은 Django의 SimpleTestCase에서 상속되며 다른 테스트 케이스 클래스와 혼합해야 할 가능성이 높습니다.

Example

from django.urls import include, path, reverse
from rest_framework.test import APITestCase, URLPatternsTestCase


class AccountTests(APITestCase, URLPatternsTestCase):
    urlpatterns = [
        path('api/', include('api.urls')),
    ]

    def test_create_account(self):
        """
        Ensure we can create a new account object.
        """
        url = reverse('account-list')
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)

Testing responses

Checking the response data

테스트 response의 유효성을 확인할 때 완전히 렌더링된 response를 검사하는 것보다 response가 생성된 데이터를 검사하는 것이 더 편리한 경우가 많습니다.

예를 들면 response.data를 검사하는 것이 더 쉽습니다.

 

response = self.client.get('/users/4/')
self.assertEqual(response.data, {'id': 4, 'username': 'lauren'})

검사 대신 response.content을 구문 분석한 결과

response = self.client.get('/users/4/')
self.assertEqual(json.loads(response.content), {'id': 4, 'username': 'lauren'})

Rendering responses

APIRequestFactory를 사용하여 뷰를 직접 테스트하는 경우 템플릿 response의 렌더링이 Django의 내부 request-response 주기에 의해 수행되기 때문에 반환되는 response는 아직 렌더링되지 않습니다. response.content에 엑세스하려면 먼저 response를 렌더링해야 합니다.

view = UserDetail.as_view()
request = factory.get('/users/4')
response = view(request, pk='4')
response.render()  # Cannot access `response.content` without this.
self.assertEqual(response.content, '{"username": "lauren", "id": 4}')

Configuration

Setting the default format

테스트 요청에 사용되는 기본 형식은 TEST_REQUEST_DEFAULT_FORMAT 설정 키를 사용하여 설정할 수 있습니다. 예를 들어, 기본적으로 테스트 요청에 항상 standard multipart form 요청 대신 JSON을 사용하려면 settings.py 파일에서 다음을 설정합니다.

REST_FRAMEWORK = {
    ...
    'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}

Setting the available formats

multipart 또는 json request 이외의 것을 사용하여 요청을 테스트해야 하는 경우 TEST_REQUEST_RENDERER_CLASSES 설정을 지정하여 테스트할 수 있습니다. 예를 들어, 테스트 요청에서 format='html'사용에 대한 지원을 추가하려면 settings.py 파일에 이와 같은 항목이 있을 수 있습니다.

REST_FRAMEWORK = {
    ...
    'TEST_REQUEST_RENDERER_CLASSES': [
        'rest_framework.renderers.MultiPartRenderer',
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer'
    ]
}

링크 :

https://www.django-rest-framework.org/api-guide/testing/#testing

 

Testing - Django REST framework

 

www.django-rest-framework.org

 

반응형

'Django > Django REST framework' 카테고리의 다른 글

DRF 테스트코드 개발 도전기  (0) 2021.12.17
14-Authentication  (0) 2021.09.26
12-Validators  (0) 2021.08.18
11. Serializer relations  (0) 2021.07.18
10. Serializer fields  (0) 2021.07.16

댓글