-
Django unittest pytestDjango 2022. 9. 28. 00:06
unittest vs pytest
둘 다 파이썬 테스트 코드 라이브러리이며
unittest는 파이썬 표준 라이브러리에 포함된 라이브러리
pytest는 2007년 첫 커밋을 이후로 계속 활발히 커밋되고 있는 라이브러리이다.
그래서 둘의 차이는 무엇이고 무엇을 사용할까?
django에서 기본적으로 unittest 모듈이 내장되어있다.
하지만 기본적으로 내장된 unittest를 사용하지 않고 pytest를 사용하는 소스들이 많다. (pandas, SQLAlchemy 등)
이유가 무엇일까 알아보자.
unittest
- Boiler Plate가 존재한다. (재사용 가능한 프로그램)
- 모든 test가 단일 Class를 상속받아 구현된다.
- assert 비교문들을 assetEqual(), assertGreater()등을 사용해 적재적소에 사용해주어야 한다.
- 권장하지 않는 방식인 카멜 케이스로 단어를 구분한다.
- 실행이 순차적으로 진행되니 규모가 커지면 느려진다.
pytest
- function 단위로 test를 작성할 수 있다.
- 단순 assert로 비교할 수 있다.
- pytest만의 고유한 방식이 존재한다. (fixture: 테스트에 필요한 부분들을 가지고 있는 소스)
- 고유한 방식이 독특하지만 강력한 장점들을 제공한다. (재사용성, 모듈화 된 방식 등)
- 매개변수화된 fixture를 사용해 여러 번의 테스트를 수행하도록 만들 수 있다.
- 테스트를 병렬적으로 실행할 수 있도록 한다 테스트의 시간이 단축된다.
- pytest로 unittest로 작성된 테스트 코드를 돌릴 수 있는 기능도 제공한다.
둘의 장단점을 비교하며 어떤 것을 사용할지 결정하는 부분에서 많은 생각을 했다.
unittest는 간단히 사용해본 경험이 있었고 pytest는 처음인 상황이고 pytest의 고유한 방식이 바로 이해가 되지는 않았다.
하지만 pytest가 가진 fixture, factory boy 등의 기능으로 간결하고 재사용성 있는 파이썬스러운 코드로 작성하여 사용해보자라는 생각과 pytest로도 unittest를 돌릴 수 있으므로 pytest를 사용해보기로 결정했다.
pytest
간단히 pip로 pytest를 설치하고 시작한다.
부가세를 부여하는 함수가 있다고 가정해보자
from decimal import Decimal def charge_vat(amount): return amount + (amount * Decimal("0.1"))import pytest def test_charge_vat(): assert charge_vat(amount=1000) == 1100위의 방식대로 구현한 후 테스트 파일이 있는 디렉터리로 들어가 pytest filename.py를 입력해주면 테스트가 동작한다.
클래스 형식으로도 구현해보자.
import pytest class TestChargeVat: def test_charge_vat(self): assert charge_vat(amount=1000) == 1100테스트 코드들은 tests라는 폴더를 따로 생성하여 관리해주는 것을 권장한다.
pytest fixture?
pytest의 fixture는 간단히 말하자면 테스트할 코드에 필요한 리소스라고 할 수 있다.
scope라는 것이 존재하는데 fixture가 실행되는 범위를 뜻한다.
@pytest.fixture(scope="function") # 함수 단위로 1회 생성된다 default @pytest.fixture(scope="class") # 클래스 단위로 1회 생성된다 @pytest.fixture(scope="module") # 파일 단위로 1회 생성된다 @pytest.fixture(scope="package") # 패키지 단위로 1회 생성된다 @pytest.fixture(scope="session") # test session동안 1회 생성된다또한 fixture로 정의한 함수는 parameter처럼으로도 사용할 수 있다.
import pytest @pytest.fixture def amount_total(): return 10000 def test_charge_vat(amount_total): assert charge_vat(amount_total) == 11000이렇게 fixture를 정해두어 다른 테스트 코드에서도 재사용을 할 수 있다.
@pytest.mark.parmetrize를 이용하여 다수의 케이스의 테스트를 실행할 수도 있다.
from decimal import Decimal @pytest.mark.parametrize("amount", [1000, 2000, 3000], indirect=True) def test_charge_vat(amount): assert charge_vat(amount) == (amount + (amount * (Decimal * 0.1))그러나 우리가 서비스하는 django 환경에서는 임시 모델 인스턴스들이 필요하다.
예를 들어 유저 10명이 필요한 테스트라면 User.objects.create()를 10번 해주어야 한다.
그럴 때 사용하는 것이 pytest factory이다.
factory boy란 fixture 데이터를 생성하기 위한 라이브러리이다. 데이터를 쉽게 생성할 수 있으며 ORM을 지원한다.
유지 관리하기 편해지며 복잡한 데이터들을 쉽게 생성할 수 있다.
또한 기본적으로 django model과 비슷하게 생겨 쉽게 이해가 가능하다.
from factory import Faker, post_generation, SubFactory from factory.django import DjangoModelFactory class UserFactory(DjangoModelFactory): # User의 Factory 모델입니다. class Meta: model = User team = SubFactory(TeamFactory) name = Faker("name") power = Faker("pyint", min_value=100, max_value=100000, step=10) is_active = True @post_generation def profile(obj, create, extracted, **kwargs): if (not create) or (extracted is False): return ProfileFactory(user=obj)Faker로 임의의 값을 생성한다.
SubFactory로 연관 모델을 같이 생성할 수 있다.
@post_generation으로 생성된 객체를 인수로 사용하여 주어진 함수를 호출할 수 있다.
여러 가지 기능들이 있고 잘 버무려서 사용하다 보면 보다 더 재사용성이 있는 테스트 코드를 작성할 수 있을 것이다.
'Django' 카테고리의 다른 글
Django Redis cache (1) 2023.01.03 Django timezone (0) 2022.10.14 Django UniqueConstraint (0) 2022.10.07 Django Signal (1) 2022.09.29 Django ORM Manager, QuerySet (2) 2022.09.26