본문으로 바로가기

Django에 Unittest 적용해보기

category Frame Work/Django 2021. 4. 4. 21:45
728x90
반응형

목차

    Django test를 적용하는 게 맞을까? unit-test를 적용하는 게 맞을까?

    개요

     

    이번엔 django에 unittest를 적용해보고 적용하는 과정에서 알았던 내용들을 기록해보려고 한다.

     

    그냥 Django Test를 적용하면 되지 않나?

    맞다. Django에서는 Test를 위해 다음과 같은 라이브러리를 제공하고 있었다.

    from django.test import TestCase
    
    class SomthingTest(TestCase):
    	...

    그러나 위와 같이 테스트를 진행하게 될 경우 Django 내부적으로 테스트를 위한 DB를 만들기 때문에 테스트해야 될 케이스가 많은  경우 Test 코드를 실행하고 완료하기까지의 시간이 오래 걸린다.

     

    물론 기존에 사용하고 있는 DB를 test용 DB로 설정하고 사용해도 되지만 그럴 경우 django의 migrate가 일어나서 필요 없는 테이블이 그대로 기존에 사용 중인 DB에 적용되어 버린다. 

     

    Unit test를 적용해보자.

     

    Unit test를 django에 적용하기

    앞서 언급한 상황에서 테스트를 적용하기 위해선 Django Test를 사용하는 것보다 unittest를  적용해 보는 것을 목표로 했다. 특별히 설정해줘야 할 것은 없었고 다음과 같은 코드를 사용해 unittest를 사용할 수 있었다.

    import unittesst
    
    class SomthingTest(unittest.TestCase):
    	...

     

    Unit test를 적용해 되는데

    그런데 unittest를 적용해보고자 하니 의문점이 생겼다 unittest는 django와는 별개의 module이기 때문에 어떻게 Test 하고자 하는 View에다가 Request를 날려야 하는지가 자연스레 궁금해졌다.

     

    단순히 django.test 라이브러리의 Client 클래스를 사용하면 해결됐다.

    import unittest
    from django.test import Client
    
    class SomethingTest(unittest.TestCase):
    	
        def setUp(self) -> None:
        	request = Client()
            response =  request.get(path, params, **headers)
        	self.result = response
        
        def test_somthing(self) -> None:
        	pass

     

    사용자 정의 Header에서 일어나는 문제

    여기까지 설정하고 Test를 수행할 때 Header를 처리하는 부분에서 문제가 생겼다. 

    headers = {"x-access-token": VALUE}
    result = requqest.get(end_point, params, **headers)

    예를 들어 위와 같이 header를 직접 지정해서 Request를 진행할 경우 x-access-token의 value를 읽지 못하는 현상이 발생했다. 이유는 django 내부적으로 header의 key값에 HTTP가 붙어있는지 체크하기 때문이다.

     

    아래는 django에서 Request 요청에서 header 정보를 꺼낼 때 사용되는 HttpHeaders 코드다.

    # django.http.request의 HttpRequest
    class HttpRequest:
        """A basic HTTP request."""
        ...
    
        def __init__(self):
            ...
            self.META = {}
           	...
    
        @cached_property
        def headers(self):
            return HttpHeaders(self.META)
    
    # django.http.request의 HttpHeaders 
    class HttpHeaders(CaseInsensitiveMapping):
        HTTP_PREFIX = 'HTTP_'
        # PEP 333 gives two headers which aren't prepended with HTTP_.
        UNPREFIXED_HEADERS = {'CONTENT_TYPE', 'CONTENT_LENGTH'}
    
        def __init__(self, environ):
            headers = {}
            for header, value in environ.items():
                name = self.parse_header_name(header)
                if name:
                    headers[name] = value
            super().__init__(headers)
    
        def __getitem__(self, key):
            """Allow header lookup using underscores in place of hyphens."""
            return super().__getitem__(key.replace('_', '-'))
    
        @classmethod
        def parse_header_name(cls, header):
            if header.startswith(cls.HTTP_PREFIX):
                header = header[len(cls.HTTP_PREFIX):]
            elif header not in cls.UNPREFIXED_HEADERS:
                return None
            return header.replace('_', '-').title()

    parse_header_name() 메서드에서 header가 'HTTP_'로 시작하는지 검사를 수행한다. 조금 더 자세한 정보는 이와 관련된 django ticket인 https://code.djangoproject.com/ticket/20147에서 참고하자. 

     

    그렇기 때문에 TestClient에서 Reuqest를 보낼 때는 다음과 같이 'HTTP_'라는 문자열을 붙여주자

    headers = {"HTTP_x_acess-token": some_value}

     

    사실은..

    사실 욕심 좀 내서 pytest를 다뤄보고 싶었다. unittest는 알고만 있었지 실제로 어떤 프로젝트와 결합해 사용해 본 적이 없었기 때문에 이번에는 우선적으로 unittest를 적용해 보고 pytest는 기회가 있을 때 다뤄보기로 하자.

     

    3줄 요약

    1.  Django-ORM을 사용하지 않는 상황에서의 Test를 해보고자 함 그래서 unittest.TestCase를 사용
    2. unittet.TestCase를 사용하려고 보니 request를 사용할 수 없음 그래서 django.http import Client를 씀
    3. Client() 객체의 Argument를 잘 설정하고 보낸 줄 알았는데 Request가 안 일어남 혹시나 해서 header를 보낼 때 "HTTP_"를 붙여서 보내보니 성공함

     

    728x90
    반응형