본문 바로가기

프로그래밍/language

단위 테스트 pytest-django 튜토리얼 - [1]

해당 포스트는 Django Framework 에 대한 기초적인 지식을 필요로한다.

아래의 링크를 통해 튜토리얼의 소스코드를 clone 할 수 있다.

https://github.com/deadlylaid/testing

단위테스트란 무엇인가?

단위테스트는 어플리케이션 내에 작성되어있는 코드가 올바르게 작동하고 있는지를 반복적으로 확인하기 위해 존재한다. 일반적으로 어플리케이션이 동작하는 데에는 무수히 많은 코드와 함수, 클래스등이 존재할 것이며 단위테스트는 이 복잡한 코드의 신뢰성을 보장하는 역할을 맡는다. 이러한 존재 의의로 인해 단위테스트는 '되도록' 지켜야할 몇 가지 특징들이 있다.

  • 자동화 되어야한다.
  • 테스트 되는 코드와 분리되어야 한다.
  • 하나의 테스트는 하나의 기능 단위만 테스트해야 한다.
  • 각 테스트는 서로 독립적이어야한다.
  • 그 외

단위테스트를 진행하면서 유의해야할 점은, 단위테스트는 단순히 코드의 신뢰성만 보장할 뿐, 각각의 코드 단위가 실행되어 어플리케이션 기능이 올바르게 동작하는지는 신경쓰지 않는다는 점이다. 단위테스트는 단순히 짜여진 범위 내의 작은 단위만 검증할 뿐 각 단위가 모두 올바르게 연결되어 실행되는지는 검증하지 않는다.

Pytest

파이썬 표준 라이브러리인 unittest보다 좀 더 효율적으로 테스트를 작성하기 위해 만들어졌다. 이번 포스팅에서는 pytest를 이용하여 python 프레임워크인 django 단위 테스트를 진행해보려 한다.

선행적으로 설치되어야하는 목록은 아래와 같다.

  • django
  • pytest
  • pytest-django (pytest의 django 확정 플러그인)

django-admin 명령어를 통해 프로젝트를 만든 후 Masterpice app을 생성하고 간단한 MTV 로직에 맞는 코드를 작성한다.

# model.py
class Masterpiece(models.Model):
    name = models.Charfield(max_length=20)
    author = models.Charfiled(max_length=20)
    create_at = models.DataTimeField(auto_now_add=True)
    price = models.models.DemicalField(decimal_places=2, max_digits=15)


# form.py
class MasterpieceModelForm(forms.ModelForm):
    class Meta:
        model = Masterpiece
        fields = '__all__'


# view.py
class MasterpieceCreateView(CreateView):    
    model = Masterpiece
    template_name = 'home.html'
    form_class = MasterpieceModelForm


class MasterpieceListView(ListView):
    model = Masterpiece
    template_name = 'home.html'

 

일반적으로 pytest는 pytest.ini 를 이용하여 여러 설정을 추가할 수 있는데, django 테스트를 위해서 DJANGO_SETTINGS_MODULE 을 추가하여 django 프로젝트 테스트 setting을 설정한다.

 

; 파일의 위치는 manager.py 와 동일하다.
[pytest]
DJANGO_SETTINGS_MODULE = masterpiece.masterpiece.settings
python_files = *tests.py

 

처음 진행하게 될 단위 테스트는 아래와 같다.

  • Masterpiece object create test
  • CreateView Get, Post request test
  • ListView Get request test

첫 테스트로 먼저 테이블 오브잭트가 올바르게 생성되는지를 확인하려고 한다.

 

def test_masterpiece_listview(client):
    obj = Masterpiece.objects.create(
        name='Creation of Adam',
        author='Michelangelo',
        price='10.99'
    )
    assert obj

 

오브젝트를 생성한 후, 해당 오브젝트가 정상적으로 생성되었는지 확인한다. pytest 명령어를 통해 테스트를 진행하면 예상치 못한 결과를 얻게 된다.

 

_ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <django.db.backends.sqlite3.base.DatabaseWrapper object at 0x104ae7710>,...

    def _cursor(self, name=None):
>       self.ensure_connection()
E       Failed: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.

../boot/lib/python3.7/site-packages/django/db/backends/base/base.py:233: Failed
====================== 1 failed in 0.39 seconds =======================

 

기본적으로 pytest는 데이터베이스에 접근 권한이 없기 때문에, 오브젝트를 생성할 수도, 가져오지도 못한다. 때문에 이 문제를 해결하기 위해서는 몇 가지 방법이 있는데 가장 쉬운 방법은 pytest의 markers 를 이용해서 db접근이 가능하게 만들어주는 것이다.

markers 란
pytest의 markers는 테스트 함수에 metadata를 손쉽게 설정할 수 있도록 도와준다. 
pytest에는 builtin makrers 가 몇 가지 존재하지만 필요하다면 스스로 marker를 만드는 것도 가능하다.

 

import pytest

@pytest.mark.django_db
def test_masterpiece_listview(client):
    obj = Masterpiece.objects.create(
        name='Creation of Adam',
        author='Michelangelo',
        price='10.99'
    )
    assert obj

 

그 다음 ListView 테스트를 진행해보려고 한다. 단순히 오브젝트를 가져와 리스팅하는 기능을 갖기 때문에 복잡할 것이 없다.

 

def test_listview_get_test(client):
    resp = client.get(
        reverse('list')
    )
    assert resp.status_code == 200

 

본래 status code 뿐 아니라 리스팅 여부도 테스트해야 하지만, 해당 내용에 대한 자세한 사항은 다음 포스팅때 하기로 한다.

다음으로 진행하게 될 테스트는 CreateView[GET, POST] 에 대한 테스트를 진행한다. 테스트하게 될 단위에는 modelform 검증도 함께 진행된다.

 

def test_modelform_test():
    data = MasterpieceModelForm(
        data={
            'name': 'Creation of Adam',
            'author': 'Michelangelo',
            'price': '100.99',
        }
    )
    assert data.is_valid()


def test_createview_get_test(client):
    resp = client.get(reverse('create'))
    assert resp.status_code == 200
    assert isinstance(resp.context_data.get('form'), MasterpieceModelForm)


@pytest.mark.django_db
def test_createview_post_test(client):
    resp = client.post(
        reverse('create'),
        data={'name': 'Creation of Adam', 'author': 'Michelangelo', 'price': '100.99'}
    )
    assert resp.status_code == 302
    obj = Masterpiece.objects.first()
    assert isinstance(obj, Masterpiece)
    assert resp.url == reverse('list')

 

test_createview_post_test 에서 오브젝트의 생성 여부와, MasterpieceListView로 이동하는 것 까지 테스트했다. 해당 테스트 역시 오브젝트를 생성하기 위해 데이터베이스에 접근해야 하므로 markers 를 사용한다.

 

이상으로, pytest-django 간단한 튜토리얼 1부를 마친다.

다음 2부에 작성될 내용은 pytest에서 사용되는 fixture의 기능에 대해 알아볼 것이다.