형도 그랬단다 죽고 싶었지만 견뎌보니 괜찮더라
[ 노라조, "형" ]
되돌아 온 위코드 데이. 속절없이 날짜는 지나가고 그보다 시간은 더 빠르게 지나갔다. 진전이 없는 코드는 쌓여만 가는 페이지 방문 기록을 무색하게 만들고, 투자한 시간마저도 빛 바랜 기억의 편린으로 만들어 버렸다. 오늘로써 3일째, 출근도 안하는데 코드마저 발전이 없다면 내가 사장이라도 월급 주기 싫을 것 같기에, 닥치는대로 코드를 밀어 넣어 보기로 했다.
RequestFactory
모듈은 Django에서도 제공하는 TestCode 관련 지원 모듈이지만, 써본 적은 없었다. 사실상 TestCase
말고는 써보지 않았으니, 모르는게 더 많을 수 밖에. DRF에서는 대부분 Django에 존재하는 모듈에서 확장된 모듈을 제공하는데, TestCase => APITestCase, RequestFactory => APIRequestFactory, Client => APIClient
처럼 API
가 다 붙어있다. 여하튼, 코드란 자고로 글만 읽어서는 알 수가 없다. 써보자, 일단.
(First)먼저, TestCase
의 확장 모듈인 APITestCase
부터 적용해 보았다.
tests.py
class TestSchool(APITestCase):
def setUp(self):
self.user = User.objects.create_user(username='testuser', password='password@123')
self.token = Token.objects.get(user__username=self.user)
self.client.credentials(HTTP_AUTHORIZATION='Token' + self.token.key)
self.school = models.School.objects.create(school_name='서울대',(etc..))
def test_school_create(self):
data = {'school_name' : '서울대' }
response = self.client.post(reverse('/school/', data))
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
setUp
부터 난관 이었는데, 나의 model
에는 존재하지 않는 User
와 Token
은 어디서 가져오는지, credentials
는 무엇이고, response
의 reverse
는 또 무엇인지 쉽게 이해할 수 없었다. 해서, 오늘은 'User' 와 'Token', 그리고 'credentials'까지 이 세 가지부터 알아보려고 한다.
User
, Token
테이블분명 models.py
에는 이 두 가지 모델명은 존재하지 않는다. 그래서 위의 테스트를 그대로 실행시키게 되면,
AttributeError: Manager isn't available; 'auth.User' has been swapped for 'schools.School'
이런 에러를 만나게 된다. 회사의 기존 코드를 클론 받아서 이어나가고 있지만, 전체 코드를 살펴보지는 못했기에, 어디엔가 내가 모르는 설정이 되어 있는듯 했다.
auth.User
를 찾아 헤맨 끝에, settings.py
에서 auth.User
를 사용하기 위해 설정해 주는 코드가 있다는 것을 알아냈고 찾아보았더니 역시나, 다음과 같은 설정이 되어있었다.
AUTH_USER_MODEL = 'schools.School'
이것을 'Custom user model'이라고 하고 이 경우에는 다음의 코드를 'setUp'에 추가로 작성해 주어야 한다.
from django.contrib.auth import get_user_model
User = get_user_model()
커스텀 된 유저 모델을 가져와서 적용해주는 모듈 'get_user_model'을 import 하고 'User' 를 변수로 지정해서 사용하려는 모델을 담아준다.
StackOverflow - 관련 답변
'Token' 역시 테이블이 존재하지 않으며, 때문에 추가 설정을 필요로 한다.
from rest_framework_simplejwt.tokens import AccessToken
self.token = Token.objects.get(user__username=self.user)
(위의 코드를 아래의 코드로 바꿔줘야 한다.)
self.access = AccessToken.for_user(self.user)
현재 회사에서 'simplejwt'라는 모듈을 사용하고 있어서 적용하는 방법을 알아보았더니, 'AccessToken'을 임포트 하면 된다는 것을 알아냈다. 이렇게 하면 굳이 'Token' 가짜 테이블을 'auth.token'으로 설정해주지 않아도 알아서 테스트용 토큰이 생성되고 사용 할 수 있게 된다.
이 메소드에 대한 해답은 공식문서에 나와있다.
credentials 메소드는 테스트 클라이언트가 모든 후속 요청에 포함 할 헤더를 설정하는데 사용할 수 있습니다.
credentials를 다시 호출하면 기존 credentials을 덮어 씁니다.
인수없이 메서드를 호출하여 기존 credentials의 설정을 해제할 수 있습니다.
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으로 설정하세요.
DRF 공식문서 번역본
즉, 테스트 실행 시, 후속으로 실행 될 모든 테스트 함수에 포함할 헤더를 설정하는 메소드이다. 인증을 통해 'Token'을 발급하면 인가된 API를 사용할 수 있는데, 이때 'Token'을 헤더에 담아서 매 API마다 로그인을 하지 않아도 되게 하는 것이 인증&인가의 기본 개념인것 처럼, credentials
메소드가 자동으로 헤더에 'Token'을 담아 주는 것이다.
이제 실행이 가능하도록 정제된 'setUp' 코드를 보도록 하자.
class ProposeGoodShopTestCase(APITestCase):
def setUp(self):
User = get_user_model()
self.user = User.objects.create_user(username='test', password='Password!')
self.access = AccessToken.for_user(self.user)
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.access}')
이로써 APITestCase
의 'setUp'이 설정 되었다.
내일은 1차로 완성된 테스트 코드를 살펴 보겠다.
살다보면 살아가다보면 웃고 떠들며 이 날을 넌 추억할테니
[ 노라조, "형" ]