소셜 로그인 유닛테스트

1

1. Mock이 대체 뭘까?

참고 블로그
1. 사전적 뜻 : 모조품

2. 유닛 테스트 할 때 왜 Mock을 쓸까?

  1. Mocking : 사전적 정의에 맞게 볼 때 유닛 테스트 시 외부에 의존(프론트엔드와의 통신 등) 부분을 '가짜로' 대체해서 해주는 기법. 즉 외부로부터 의존하지 않고 독립적으로 테스트가 가능한 기법

  2. 어떻게 불러올까?

>>> from unittest.mock import Mock, MagicMock
  1. 사용법
  • mock이라고 불리는 가짜 객체를 만드는 것부터 시작. 이 객체가 어떻게 작동할지 지정 가능하며 어떤 작업이 일어났는지 기억해 줌.
>>> from unittest.mock import Mock
>>> mock=Mock(return_value='Hello, Mock!')
>>> mock()
'Hello, Mock!'
  • MagicMock : 목은 기본적으로 __str__ 과 같은 매직 메서드가 자동으로 모킹되지 않는다. 하지만 MagicMock은 불편함을 감수해주는 좋은 클래스다. 매직 메서드를 미리 알아서 모킹해주기 때문이다.
>>> from unittest.mock import MagicMock
>>> mock = MagicMock()
>>> mock.__str__.return_value
"<MagicMock id='140231244167728'>"
>>> mock.__str__.return_value = "I'm a magic mock"
>>> str(mock)
"I'm a magic mock"
  • 물론 mock을 이용한 혹은 magicmock을 이용한 모킹이 유닛 테스트에서 사용이 가능하긴 하지만 실제로 patch라는 데코레이터를 사용하는 것이 훨씬 더 효과적이다. 왜 효과적인지 패치를 한 번 살펴보자.

3. patch는 뭘까?

참고 블로그

  1. patch 데코레이터
    모킹과 비슷하게 외부로부터 독립적으로 유닛 테스트를 진행할 수 있지만 큰 차이점은 데코레이터의 특정 범위 내에서만 모킹을 해주게 하는 점이 큰 차이점이다.

  2. 그럼 왜 쓸까? -> 플로우 이해

  • 우리가 가지고 있는 데이터는 mock형태의 가짜 데이터. 즉 카카오 API와 바로 소통할 수 없는, return할 수 없는 데이터다. 근데 view 로직 상 유효한 소통이 있어야한다.
  • 그래서 우리는 가짜 카카오를 만들었다.
  • 그럼 실제 API와 소통하는 방식이 필요한데 이를 어떻게 쓸 것인가?
  • 그 과정을 끌어 쓰는 것을 @patch 데코레이터가 해주는 것이다.
  1. 예시(위 블로그 예제 참고)
from unittest import TestCase, main
from unittest.mock import patch


def hello():
    return "Hello!"


class TestMe(TestCase):
    @patch("__main__.hello", return_value="Mock!")
    def test_hello(self, mock_hello):
        self.assertEqual(hello(), "Mock!")
        self.assertIs(hello, mock_hello)
        mock_hello.assert_called_once_with()


if __name__ == "__main__":
    main()

원래 "Hello!"을 리턴하는 hello() 함수가 "Mock!"를 대신 리턴하도록 @patch() 데코레이터로 patching을 하고 있다.

@patch() 데코레이터를 사용하여 patching을 하게되면 mock객체를 테스트 메서드의 인자로 추가하게 되는데, 위 예제 코드에서 mock_hello가 이 mock객체의 매개변수명으로 쓰이고 있다.
첫번째 인자 : patching할 메서드를 package.module.Class.method 형태의 문자열로 받는다.

어려운 내용이다. 일단 내가 사용하였던 코드가 어떻게 적용되는지 보자.

4. 소셜 로그인 테스트 코드

import json

from django.test    import TransactionTestCase, Client
# transationtTestCase를 사용한 이유 : 연속된 과정에서 나는 에러를 방지

from unittest.mock  import patch, MagicMock
# unittest.mock 관련한 것 import

from user.models import User
# 필요한 것 import


class KakaoLoginTest(TransactionTestCase):
# fake 데이터 만들기
    def setUp(self):
        User.objects.create(
            username = '안다민',
            kakao_id = '1234567890',
            email = 'damin0320@kakao.com'
        )
        
    def tearDown(self):
        User.objects.all().delete()
  	# 없애기
        
    @patch('user.views.requests')
    def test_kakao_login_success(self, mocked_requests):
        client = Client()
        class MockedResponse:
            def json(self):
                return {
                    "id" : "1234567890",
                    "properties" : {
                        "nickname" : "안다민"
                    },
                    "kakao_account" : {
                        "email" : "damin0320@kakao.com"
                    }
                }
                # 비교 가능한 가짜 반응 클래스로 리턴
        mocked_requests.get = MagicMock(return_value=MockedResponse())
        # 가짜 데이터와의 송수신
        header = {'HTTP_Authorization' : 'access_token'}
        response = client.post('/user/signin', content_type='application/json', **header)
        self.assertEqual(response.status_code, 200)
        # 로그인 성공에 대한 리턴
        
   
    def test_kakao_login_fail(self):
        client   = Client()
        header   = {'No_Authorizaeion' : '1234'}
        response = client.post('/user/signin', content_type='application/json', **header)
        self.assertEqual(response.status_code, 400)
        # 로그인 실패 시 리턴

중간 중간 필요한 내용들이 어떻게 나오는지 print 해보면서 진행해야 한다. 유닛테스트를 자주 해주면서 익숙해지고 눈에 익어야 할 것 같다. 아직 감이 오진 않는 정도고 쓸 수 있다? 정도까지 인 듯.

profile
커피 내리고 향 맡는거 좋아해요. 이것 저것 공부합니다.

0개의 댓글