[django | user app] Build a Backend REST API - 23

Hyeseong·2021년 3월 3일
0
post-custom-banner

Add tests for create user API🎐

이제 본격적으로 user api작성을 해볼건데요. 근데 항상 그 처음은 뭘까요?
TDD(Test Driven Development) 방식으로 진행된다는 점!

app/user/tests/ 폴더에서 새로운 파일 test_user_api.py을 만들어 볼게요.

오늘 완성할 코드는 아래와 같아요.
하나씩 분석해 봐야겠조?

from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse

from rest_framework.test import APIClient
from rest_framework import status


CREATE_USER_URL = reverse('user:create') # create `user create` URL 
TOKEN_URL = reverse('user:token')


def create_user(**params):
    return get_user_model().objects.create_user(**params)


class PublicUserApiTests(TestCase):
    """Test the users API (public)"""

    def setUp(self):
        self.client = APIClient()

    def test_create_valid_user_success(self):
        """Test creating user with valid payload is successful"""
        payload = {
            'email': 'test@testtest.com',
            'password': 'testpass',
            'name': 'Test name',
        }
        res = self.client.post(CREATE_USER_URL, payload)

        self.assertEqual(res.status_code, status.HTTP_201_CREATED)
        user = get_user_model().objects.get(**res.data)
        self.assertTrue(user.check_password(payload['password']))
        self.assertNotIn('password', res.data)

    def test_user_exists(self):
        """Test creating a user that already exists fails"""
        payload = {'email': 'test@testtest.com', 'password': 'testpass'}
        create_user(**payload)

        res = self.client.post(CREATE_USER_URL, payload)

        self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

    def test_password_too_short(self):
        """Test that the password must be more thatn 5 characters"""
        payload = {'email': 'test@testtest.com', 'password': 'pw'}
        res = self.client.post(CREATE_USER_URL, payload)

        self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
        user_exists = get_user_model().objects.filter(
            email=payload['email']
        ).exists()
        self.assertFalse(user_exists)


step1 import🤕

  • TestCase 클래스를 임포트할게요. 이 클래스가 부모클래스가 되어 다른 클래스에 상속을 해줄거에요.

  • get_user_model은 User클래스를 활용하기 위해 임포트할게요.

  • reverse() 메소드는 API URL을 만들어 줄거에요.

  • APIClient 클래스는 restframework 테스트를 도와주는 tool 역할을해요.

  • 그리고 status를 임포트할게요.이전에는 단순한 숫자, 200, 201, 400, 404사용했지만 rest_framework를 이용하면 더 직관적으로 단어와 문자까지 명시해줘요.

from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse

from rest_framework.test import APIClient
from rest_framework import status

step2 let's do it😀

CREATE_USER_URL = reverse('user:create') # create `user create` URL 
TOKEN_URL = reverse('user:token')


def create_user(**params):
    return get_user_model().objects.create_user(**params)


class PublicUserApiTests(TestCase):
    """Test the users API (public)"""

    def setUp(self):
        self.client = APIClient()

    def test_create_valid_user_success(self):
        """Test creating user with valid payload is successful"""
        payload = {
            'email': 'test@testtest.com',
            'password': 'testpass',
            'name': 'Test name',
        }
        res = self.client.post(CREATE_USER_URL, payload)

        self.assertEqual(res.status_code, status.HTTP_201_CREATED)
        user = get_user_model().objects.get(**res.data)
        self.assertTrue(user.check_password(payload['password']))
        self.assertNotIn('password', res.data)

    def test_user_exists(self):
        """Test creating a user that already exists fails"""
        payload = {'email': 'test@testtest.com', 'password': 'testpass'}
        create_user(**payload)

        res = self.client.post(CREATE_USER_URL, payload)

        self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

    def test_password_too_short(self):
        """Test that the password must be more thatn 5 characters"""
        payload = {'email': 'test@testtest.com', 'password': 'pw'}
        res = self.client.post(CREATE_USER_URL, payload)

        self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
        user_exists = get_user_model().objects.filter(
            email=payload['email']
        ).exists()
        self.assertFalse(user_exists)

임포트 할 것들을 어느정도 했다면 이제는 본격적으로 작성해볼게요.

상수 두개 CREATE_USER_URLTOKEN_URL를 정의하겠습니다.
소문자를 쓰지 않는 이유는 테스트하는 동안 해당 변수가 전혀 변하지 않기에 대문자로 상수로 만든거에요.

그리고 둘다 reverse() 메서드를 사용해요.안에 'user:create' 문자를 넣으면 하드타이핑한 url과 동일하게 결과가 나오게 되요.
동일한 방식으로 토큰을 생성할 url도 TOKEN_URL 변수로 url을 만들게요.

helper function👾

그리고 helper 함수를 만들게요. 테스트 사용에만 국한된 함수 하나를 만들기위해서 필요해요. 특히 테스트를 매번 여러번 할때 마다 중복되는 소스코드가 있는경우가 허다해요. 그럴때 사용하기 유용한! 중복을 최소화 할 수 있는! 녀석을 만드는거조.

여기에서는 매번 테스트를 할때 각각의 유저를 길게 소스코드로 길게 풀어쓰기보단 helper function을 전역 scope에 정의해서 사용하므로써 재사용성을 UP! 하는거조.

create_user 매서드로 정의할게요. 그리고 return 할 때는 ORM을 이용해서 유저 인스턴스를 만들도록할게요.
인자는 **kwargs를 둬서 더 다이나믹하게 만들도록 하고요.

즉, get_user_model().objects.create_user(**params) 이렇게 길게 쓸거 create_user로 짧게 쓴다고 보면되요.

def create_user(**params):
    return get_user_model().objects.create_user(**params)

Making class for test😛

아래와 같이 클래스를 정의할텐데요. 이렇게 한 이유는 Public vs Private한 느낌으로 클래스를 구분짓기 위해서요.

퍼블릭은 아무나(로그인을 했든 안했든) 프라이빗은 로그인한 특정 유저들로 그룹을 지어 각각의 테스트를 구분지어 진행하기 위해서에요.

그리고 이렇게 구분지을수록 더 명확하고 코드의 직관성과 clean함이 좋아요.
더불어 setUp메서드 내부에는 self.client = APIClient()를 정의함으로써 클라이언트 호출과 사용을 더 원활하고 쉽게 할 수 있어요.

다른말로든 매번 테스트 할 때마다 APIClient() 클래스를 할당해주고 변수에 정의하는 반복작업을 할 필요가 없거든요.

class PublicUserApitests(TestCase):👺
	 """Test the users API (public)"""

    def setUp(self):
        self.client = APIClient()
       

first test🙃

이메일, 비밀번호, 이름을 입력하면 정상적으로 회원가입이 이루어지는지 확인하기 위한 테스트 코드를 짤거에요.

payload 딕셔너리 변수안에 post요청으로 회원가입시 전달할 데이터들을 정리하도록 할게요.

그 후 res = self.client.post(CREATE_USER_URL, payload)를 통해서 상수로 박았던 CREATE_USER_URL 첫 번째 인자로, 그리고 payload를 두 번째 인자로 둬서 해당 엔드포인트에 데이터를 post요청으로 던져주기로 합니다.

    def test_create_valid_user_success(self):
        """Test creating user with valid payload is successful"""
        payload = {
            'email': 'test@testtest.com',
            'password': 'testpass',
            'name': 'Test name',
        }
        res = self.client.post(CREATE_USER_URL, payload)

        self.assertEqual(res.status_code, status.HTTP_201_CREATED)
        user = get_user_model().objects.get(**res.data)
        self.assertTrue(user.check_password(payload['password']))
        self.assertNotIn('password', res.data)

그리고 post요청으로 던졌으면 response로 201을 잘 받았는지 확인해야겠조?
elf.assertEqual(res.status_code, status.HTTP_201_CREATED) 그 부분이 이거에요.

여기서 눈여겨 볼만한게 2번째 인자가 말그대로 HTTP_201_CREATED로 정의해서 매우 직관적이고 무엇인지 한눈에 파악할 수 있어요.
어쨋든 assertEqual()은 첫번째, 두번째 인자가 동일하다걸 확인해줘요.

다음은 디비에 있는 암호화된 패스워드가 payload에 있는 패스워드와 동일한지 확인 합니다.

그리고 assertNotIn()메소드를 통해서 첫번째 인자가 res.data의 키로 있는지 없는지확인해줘요. 왜냐하면 비밀번호 정보가 response로 돌아가면 안되잖아요.(encrypted되었다할지라도요.)

second test

회원가입시 유저가 이미 존재하는지 안하는지 확인하기 위한 테스트에요.
페이로드를 작성할게요.
그 다음이 중요한데요. 바로 create_user(**payload)를 호출해서 서버에서 유저 정보를 등록해버리는거조.
그리고 client에서 URL endpoint로 payload를 던져줘서 동일한 유저정보를 가지고 있는 유저를 생성하도록 post request를 던져주는거에요.

당연히 400에러가 정상적으로 나야하고 이를 확인하기 위해 asserEqual()메서드를 이용해서 테스트하게 되요.

 def test_user_exists(self):
        <"""Test creating a user that already exists fails"""
        payload = {'email': 'test@testtest.com', 'password': 'testpass'}
        create_user(**payload)

        res = self.client.post(CREATE_USER_URL, payload)

        self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

third test

3번째로 해볼 테스트는 비밀번호가 짧은 경우 일어날 테스트를 진행해볼게요.

serializer.py파일안의 meta 영역에 직접적으로 5글자를 최소 마지노선으로 잡아 놓았어요.
어쨋든 post요청으로 단 2글자로 비밀번호가 설정되서 회원가입이 되었는지 안되었는지 서버에서 확인해볼텐데요. ORM 메소드중 filter메소드를 이용해서 쿼리가 존재하는지 하지 않는지 self.assertFalse()를 통해 확인해볼거에요.

class UserSerializer(serializers.ModelSerializer):
    """Serializer for the users object"""

    class Meta:
        model = get_user_model()
        fields = ('email', 'password', 'name')
        extra_kwargs = {'password': {'write_only' : True, 'min_length': 5}}
    def test_password_too_short(self):
        """Test that the password must be more thatn 5 characters"""
        payload = {'email': 'test@testtest.com', 'password': 'pw'}
        res = self.client.post(CREATE_USER_URL, payload)

        self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
        user_exists = get_user_model().objects.filter(
            email=payload['email']
        ).exists()
        self.assertFalse(user_exists)

여기까지 작성했다면 아래 명령어를 콘솔에 찍어서 테스트 결과를 확인해볼게요.

docker-compose run --rm app sh -c "python manage.py test"

profile
어제보다 오늘 그리고 오늘 보다 내일...
post-custom-banner

0개의 댓글