Python 의 테스팅을 위한 프레임워크로 unittest, pytest가 있다. 둘 모두 테스팅을 하는데 이용되는 도구이지만 약간의 차이가 있다. 간단하게 둘의 사용용도를 나눠보자면 unittest 는 간단한 테스트에, pytest는 좀 더 구조화된 테스트 환경을 조성할 때 쓴다고 볼 수 있다.
pip 를 이용하여 pytest
를 받을 수 있다.
pip install -U pytest
이후
pytest --version 으로 버전을 확인할 수 있다.
> This is pytest version 7.1.0
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
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]
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 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
: 세션의 마지막에 제거된다.