๐Ÿ”ฅ TIL#13. kakao signin์— ๋Œ€ํ•œ unit test

๋ฐฑ์Šน์ง„ยท2020๋…„ 12์›” 5์ผ
0

wecode Django ์‹ค์Šต

๋ชฉ๋ก ๋ณด๊ธฐ
15/16

Today I Learned

๊ฐœ๋ฐœ์ค‘์ธ project์— 'kakao ๊ณ„์ •์—ฐ๋™ signin' ๊ธฐ๋Šฅ์ด ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด View ์—๋Š” kakao api์™€ ์—ฐ๋™ํ•˜๋Š” ๋กœ์ง์ด ๋“ค์–ด์žˆ๋‹ค. unit test ๋Œ€์ƒ์œผ๋กœ test ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ ค ํ•ด๋„ ๋กœ์ง ์ž์ฒด๊ฐ€ ์ด๋ฏธ Integration test ๋ฒ”์œ„์— ์†ํ•˜๋Š”๋ฐ ์ด๋ฅผ unit test ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ?
'kakao๋กœ ์š”์ฒญํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๋Š” ๊ณผ์ •'์„ ๊ฐ€์ƒ์ฒ˜๋ฆฌ ํ•  ์ˆ˜๋Š” ์—†์„๊นŒ?

View์—์„œ requests ๋ชจ๋“ˆ๋กœ ์š”์ฒญ ๋ฐ ์ด์— ๋Œ€ํ•œ response๋ฅผ ๋ฐ›๋Š” ๊ณผ์ •์„ ๊ฐ€์ƒ์ฒ˜๋ฆฌ ํ•˜๊ธฐ

kakao ๊ณ„์ •์—ฐ๋™ signin์„ unit testํ•˜๋Š”๋ฐ ์žˆ์–ด [์„ฑ๊ณต, ์˜ˆ์™ธ, ์‹คํŒจ] 3๊ฐ€์ง€ test๋ฅผ ์ง„ํ–‰ํ•ด์•ผ ํ•œ๋‹ค.
์ด๋ฅผ ์œ„ํ•ด ํ•ด๋‹น ๊ธฐ๋Šฅ์˜ ๋กœ์ง์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

Frontend๊ฐ€ kakao api๋ฅผ ์ด์šฉ, access_token์„ ์–ป์–ด Backend๋กœ signin ์š”์ฒญ์‹œ ํ•จ๊ณ„ ๋ณด๋‚ธ๋‹ค. Backend๋Š” kakao๋กœ access_token์„ ๋ณด๋‚ด ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌ ํ•œ๋‹ค.

[์˜ˆ์™ธ, ์‹คํŒจ] ์˜ ๊ฒฝ์šฐ frontend๋กœ๋ถ€ํ„ฐ ๊ฐ€์ƒ์œผ๋กœ ์ž˜๋ชป๋œ access_token์„ ๋ฐ›๊ฑฐ๋‚˜ ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๋Š” ๊ฒƒ์œผ๋กœ Test๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
[์„ฑ๊ณต]์˜ ๊ฒฝ์šฐ ๊ฐ€์ƒ์œผ๋กœ kakao๋กœ๋ถ€ํ„ฐ '์‚ฌ์šฉ์ž์ •๋ณด'๋ฅผ ๋ฐ›์•„ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ ์–˜๊ธฐ๋Š” View๋‚ด๋ถ€์—์„œ kakao๋กœ ์š”์ฒญํ•˜๋Š” ๊ณผ์ •์ด ์‹คํ–‰๋˜๋ฉด ์•ˆ๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.
์–ด๋–ป๊ฒŒ ์‹ค์ œ๊ธฐ๋Šฅ์„ ๊ฐ€์ƒ๊ธฐ๋Šฅ์œผ๋กœ ์šฐํšŒํ•  ์ˆ˜ ์žˆ์„๊นŒ?

์ด๋ฅผ ์œ„ํ•ด์„  unittest module ์ค‘ patch์™€ MagicMock ์— ๋Œ€ํ•ด ์•Œ์•„์•ผ ํ•œ๋‹ค.
์ง„ํ–‰์— ์•ž์„œ project์— ๋ฐ˜์˜ํ•œ test code๋ฅผ ๋ณด์ž.

# user/tests.py

from django.test import TestCase, Client

from unittest import patch, MagicMock

class UserTest(TestCase):
    def setUp(self):
    	client=Client()
   
    @patch('user.views.requests')
    def test_user_signin_with_kakao_success(self, mocked_request):
        class FakeResponse:
            def json(self):
               return {
                        'id'            : 12345,
                        'kakao_account' : {
                            'profile'   : {
                                'nickname':'test_user',
                                'thumbnail_image_url':'image_url_info',
                                'email':'test@example.com'
                            }
                        }
                    }

            mocked_request.post = MagicMock(return_value=FakeResponse())

            headers = {'HTTP_Authorization':'fake_token.1234'}
            response = self.client.post('/user/kakao_sign_in',
                            content_type='application/json',
                            **headers
                        )

            self.assertEqual(response.status_code, 200)

์œ„๋Š” kakao ๊ณ„์ •์—ฐ๋™ signin view์— ๋Œ€ํ•ด success test code ์ด๋‹ค.
์šฐ์„  'patch' ๋ฅผ ์•Œ์•„๋ณด์ž.

patch๋Š” test ๋Œ€์ƒ ๋ชจ๋“ˆ์—์„œ ํด๋ž˜์Šค๋‚˜ ๊ฐ์ฒด๋ฅผ mockingํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” decorater์ด๋‹ค.

์œ„ code๋ฅผ ๋ณด๋ฉด user app์˜ views ๋ชจ๋“ˆ์•ˆ์— ์žˆ๋Š” requests ๋ชจ๋“ˆ์„ patch ๋Œ€์ƒ์œผ๋กœ ํ•˜๊ณ  ์žˆ๋‹ค. ์ด๋Š” views๋‚ด์˜ requests๋ฅผ mocking ํ•œ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค. ํ™•์ธ์„ ์œ„ํ•ด ์•„๋ž˜ user.views code๋ฅผ ๋ณด์ž.

# user/views.py

class SignInWithKakaoView(View):
    @transaction.atomic
    def post(self, request):
        try:
            token         = request.headers['Authorization']
            profile       = requests.post(
                                    "https://kapi.kakao.com/v2/user/me",
                                    headers = {'Authorization':f'Bearer {token}'},
                                    data    = 'property_keys=["kakao_account.email"]'
                                )
            profile       = profile.json()
            user_id       = profile.get('id', None)

            if not user_id:
                return JsonResponse({"message":"INVALID_TOKEN"}, status=400)

            kakao_account = profile.get('kakao_account')
            nickname      = kakao_account['profile'].get('nickname', '')
            thumbnail     = kakao_account['profile'].get('thumbnail_image_url', '')
            email         = kakao_account.get('email', '')
            user          = User.objects.update_or_create(
                                email    = email,
                                defaults = {
                                            'kakao_id'  : str(user_id),
                                            'nickname'  : nickname,
                                            'image_url' : thumbnail,
                                            'updated_at': datetime.now(),
                                            'email'     : email,
                                            }
                             )
            token         = generate_token(user[0].id)

            return JsonResponse({"message":"SUCCESS", "access_token":token}, status=200)

        except KeyError:
            return JsonResponse({"message":"KEY_ERROR"}, status=400)

ํ•ด๋‹น View๋ฅผ ๋ณด๋ฉด kakao๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด ์š”์ฒญ์„ ์œ„ํ•ด requests ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

์ฆ‰ unit test์‹œ์—๋Š” requests๋ฅผ patching ํ•จ์œผ๋กœ์„œ ์‹ค์ œ ์š”์ฒญ๊ณผ์ •์„ ๋ฌด์‹œํ•˜๋„๋ก ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  kakao๋กœ๋ถ€ํ„ฐ ๋ฐ›์•„์•ผํ•˜๋Š”'์‚ฌ์šฉ์ž์ •๋ณด'๋Š” MagicMock๊ณผ FakeResponse class๋ฅผ ํ†ตํ•ด mockingํ•œ๋‹ค.

MagicMock์€ Mockํ•จ์ˆ˜๋ฅผ ๋งŒ๋“œ๋Š” ๋ชจ๋“ˆ๋กœ mock data๋ฅผ returnํ•˜๋Š” ํ•จ์ˆ˜๋ผ ๋ณด๋ฉด ๋œ๋‹ค.
์ž์„ธํ•œ ์„ค๋ช…์€ Mock & MagicMock ์„ ์ฐธ๊ณ .

patching ํ•œ requests์˜ post ํ˜ธ์ถœ์— ๋Œ€ํ•œ ์‘๋‹ต๊ฐ’์œผ๋กœ Mocking data์„ ๋Œ€์ž…ํ•˜๋ฉด Test์‹œ์—๋Š” requests.post() ์˜ ๊ฒฐ๊ณผ๋กœ mock data๊ฐ€ ๋‚˜์˜ค๊ฒŒ ๋œ๋‹ค.
์ด ๋ฐฉ๋ฒ•์„ ํ†ตํ•ด requests ์— ๋Œ€ํ•œ patching, mocking ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

profile
12๋…„ .NET ๊ฐœ๋ฐœ ๊ฒฝ๋ ฅ์„ ๊ฐ€์ง„ ์›น ์ดˆ์งœ ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค :)

0๊ฐœ์˜ ๋Œ“๊ธ€