Pytest 알아보기

김지환·2022년 8월 20일
0
post-custom-banner

Python 의 테스팅을 위한 프레임워크로 unittest, pytest가 있다. 둘 모두 테스팅을 하는데 이용되는 도구이지만 약간의 차이가 있다. 간단하게 둘의 사용용도를 나눠보자면 unittest 는 간단한 테스트에, pytest는 좀 더 구조화된 테스트 환경을 조성할 때 쓴다고 볼 수 있다.

How to install?


pip 를 이용하여 pytest를 받을 수 있다.

pip install -U pytest

이후 
pytest --version 으로 버전을 확인할 수 있다.
> This is pytest version 7.1.0

How to use ?


pytest 를 사용하기 위해서는 몇 가지 rule 을 지켜주면 된다.

먼저 모듈, 함수등을 만들 때 prefix 로 test_* 를 붙여서 만들어 주면 된다. class의 경우에는 Test를 붙여주면 된다.

그 후 프로젝트의 루트에서 pytest cli 를 사용해주면 해당 Prefix가 붙은 모듈들을 모두 테스트해준다. 테스트 statement는 assert를 사용하여 만들 수 있다.

# content of test_sample.py
def func(x):
    return x + 1

def test_answer():
    assert func(3) == 5
=========================================================================== test session starts ============================================================================
platform darwin -- Python 3.8.3, pytest-5.3.5, py-1.8.1, pluggy-1.0.0
rootdir: /Users/mason/Work/test
plugins: anyio-3.4.0, typeguard-2.13.3
collected 1 item                                                                                                                                                           

test_sample.py F                                                                                                                                                           [100%]

================================================================================= FAILURES =================================================================================
_______________________________________________________________________________ test_answer ________________________________________________________________________________

    def test_answer():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test_sample.py:7: AssertionError
============================================================================ 1 failed in 0.08s =============================================================================
(env) (base)test

어떤 모듈에서 에러가 났는지 출력을 확인할 수 있다. 해당 출력을 html 형태로 뽑아서 확인해 볼 수도 있다.

전체가 아닌 특정 모듈 혹은 class 안의 메소드를 테스트하기 위해서는 아래와 방식으로 가능하다.

pytest test_mod.py::test_func
pytest test_mod.py::TestClass::test_method

Test fixture


fixture란 테스트를 실행하기전 준비해줘야하는 모든 리소스를 의미한다. 데이터베이스 설정, 변수설정, 서버설정 등 다양한 설정들이 있을 수 있다.

pytest 에서 fixtures의 활용은 pytest.fixture 데코레이터를 이용한다.

# contents of test_append.py
import pytest

# Arrange
@pytest.fixture
def first_entry():
    return "a"

# Arrange
@pytest.fixture
def order():
    return []

# Act
@pytest.fixture(autouse=True)
def append_first(order, first_entry):
    return order.append(first_entry)

def test_string_only(order, first_entry):
    # Assert
    assert order == [first_entry]

pytest가 돌 때 fixtures 를 먼저 실행하고 해당 return 값들은 caching 된다. 따라서 test_ 함수들은 이 caching 된 return 값을 사용하게 된다. autouse 를 사용하게 되면 해당 fixture를 매개변수로 사용하지 않아도 자동으로 실행하게 해주게 된다. 만약 autouse를 하지 않는다면 해당 fixture는 동작하지 않기 때문에 error가 발생하게 된다.

따라서 autouse를 True로 해주거나 혹은 test_string_only(append_first, order, first_entry) 와 같은 식으로 실행을 해줘야한다.

def test_string_only(append_first, order, first_entry):
    # Assert
    assert order == [first_entry]

return 보다는 yield를 권장


pytest에서는 fixture를 만들 때 return을 사용하기 보다는 yield를 권장한다. yield로 코드를 구현하게 되면 추가적으로 teardown 로직을 구현할 수 있는 장점이 있다.

# content of test_emaillib.py
import pytest

from emaillib import Email, MailAdminClient

@pytest.fixture
def mail_admin():
    return MailAdminClient()

@pytest.fixture
def sending_user(mail_admin):
    user = mail_admin.create_user()
    yield user
    mail_admin.delete_user(user)

@pytest.fixture
def receiving_user(mail_admin):
    user = mail_admin.create_user()
    yield user
    mail_admin.delete_user(user)

def test_email_received(sending_user, receiving_user):
    email = Email(subject="Hey!", body="How's it going?")
    sending_user.send_email(email, receiving_user)
    assert email in receiving_user.inbox

유저를 만들고 mailing test를 한 후 유저를 삭제하는 간단한 로직이다. test가 종료되면 생성된 순의 역순으로 fixture의 teardown이 진행된다.

Pytest scope 설정


또한 pytest fixtures는 scope를 설정할 수가 있는데 이 scope를 설정함에 따라 다른 모듈에서도 해당 fixture를 사용할 수 있게 된다.

# content of conftest.py
import pytest
import smtplib

@pytest.fixture(scope="module")
def smtp_connection():
    return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
# content of test_module.py

def test_ehlo(smtp_connection):
    response, msg = smtp_connection.ehlo()
    assert response == 250
    assert b"smtp.gmail.com" in msg
    assert 0  # for demo purposes

def test_noop(smtp_connection):
    response, msg = smtp_connection.noop()
    assert response == 250
    assert 0  # for demo purposes
=========================================================================== test session starts ============================================================================
platform darwin -- Python 3.8.3, pytest-5.3.5, py-1.8.1, pluggy-1.0.0
rootdir: /Users/mason/Work/test
plugins: anyio-3.4.0, typeguard-2.13.3
collected 2 items                                                                                                                                                          

test_module.py FF                                                                                                                                                    [100%]

================================================================================= FAILURES =================================================================================
________________________________________________________________________________ test_ehlo _________________________________________________________________________________

**smtp_connection = <smtplib.SMTP object at 0x7fe786e3c0d0>**

    def test_ehlo(smtp_connection):
        response, msg = smtp_connection.ehlo()
        assert response == 250
        assert b"smtp.gmail.com" in msg
>       assert 0  # for demo purposes
E       assert 0

test_module.py:8: AssertionError
________________________________________________________________________________ test_noop _________________________________________________________________________________

**smtp_connection = <smtplib.SMTP object at 0x7fe786e3c0d0>**

    def test_noop(smtp_connection):
        response, msg = smtp_connection.noop()
        assert response == 250
>       assert 0  # for demo purposes
E       assert 0

test_module.py:14: AssertionError
============================================================================ 2 failed in 1.46s =============================================================================

다른 모듈내에서 따로 import를 할필요 없이 smtp_connection instance를 가져와서 사용한다.

fixture의 scope는 5가지로 조절할 수 있다.

  • function: 디폴트 스코프로 함수 test가 종료되면 제거된다.
  • class: fixture를 사용하는 마지막 클래스의 tearDown에서 제거된다.
  • module: 마지막 module에서의 tearDown에서 제거된다.
  • package: 마지막 package에서의 tearDown에서 제거된다.
  • session: 세션의 마지막에 제거된다.

Reference

https://docs.pytest.org/en/7.1.x/getting-started.html

profile
Developer
post-custom-banner

0개의 댓글