Pytest
- Python test framework 중 하나이다.
- 관련하여 다양한 plug-in이 있다.
unit test
- 단위 테스트의 단위는, 함수 하나에 대하여 그리고 하나의 케이스에 대한 것이다.
- 테스트 함수 간에는 변수나 객체를 공유하지 않는 것이 원칙이다.
usage
def average(num_list):
return sum(num_list) / len(num_list)
from my_math import *
def test_average():
assert average([1,2,3]) == 2
- 함수 명은 'test_'로 시작하도록 한다.
$ pytest
- 현재 디렉터리를 기준으로 하위 디렉터리까지 탐색하여,
'test_'로 시작하는 파일을 찾아 단위 테스트를 실행한다.
- -v 옵션: 결과를 테스트 함수별로 자세히 확인할 수 있다.
(이외에도 유용하게 쓸 수 있는 다양한 옵션들이 있다.)
- PYTHONPATH를 찾지 못해서 ModuleNotFoundError가 발생할 수도 있다.
이 때는, python -m pytest 로 실행하면 된다.
- python -m: 명령어가 실행되는 현재 디렉터리를 sys.path에 추가하여
python 명령어를 실행한다.
- 특히, github-actions와 같은 CI/CD를 할 때 쓰이곤 한다.
pytest.ini
- pytest 명령어 옵션 등을 설정할 수 있는 파일이다.
- 테스트 디렉터리에 직접 생성해두도록 한다.
@pytest.fixture
- 테스트 환경 등의 자원을 재사용할 수 있게 해준다..
- 테스트 함수마다 객체를 생성해야 할 필요가 있을 때 등,
반복적인 코드 작성이 불필요해진다.
- conftest.py를 생성하여 여기에 작성해두면,
해당 디렉터리의 모든 테스트 파일에 적용되어 사용할 수 있게 된다.
- scope를 두어 생성 단위를 조절할 수 있다.
- 기본값은 함수 단위이며, 이 때는 테스트 함수 각각에 적용할 수 있다.
usage
class Calculator:
def add(self, x, y):
return x + y
def subtract(self, x, y):
return x - y
import pytest
from calculator import Calculator
@pytest.fixture
def calculator():
return Calculator()
- pytest가 실행될 때 fixture function을 실행하며,
아래 각 테스트 함수의 매개변수로 전달된다.
- 함수 객체를 변수처럼 사용할 수 있게 된다.
def test_add(calculator):
assert calculator.add(1, 2) == 3
def test_subtract(calculator):
assert calculator.subtract(3, 2) == 1
set-up, tear-down
- 테스트를 하기 위해서는 환경과 데이터를 set-up해줘야 하고,
테스트가 종료되었다면 이를 정리(tear-down)해줘야 한다.
fixture는 이러한 상황에서 진가를 발휘한다.
- scope를 설정하여, 의도한 테스트 단위에 맞게 set-up 및 tear-down 하도록 한다.
- tear-down을 위해, yield를 사용하곤 한다.
- yield가 return을 대신하고, 그 아래의 코드들은 각 테스트 함수가 종료된 후 실행된다.
- DB를 연결하고 종료하거나, 임시 파일/디렉터리를 생성하고 삭제하는 때에 사용한다.
- 서버 framework에서 지원하는 테스트 관련 리소스가 context mananger인 경우,
with문(+ return)을 활용한 tear-down도 가능하다.
import pytest
@pytest.fixture
def setup_and_teardown():
print('\nset up')
yield 'set'
print('\tear down')
def test_(setup_and_teardown):
assert 'set' == setup_and_teardown
test_.py::test_
set up
PASSED
tear down
- print()의 output까지 출력하기 위해, -s 옵션을 추가한다.
@pytest.mark
- 테스트 함수에 metadata를 설정할 수 있다.
{marker_name}
- 필수 테스트 함수만 실행하고자 할 때 사용한다.
markers =
<marker_name>: <comment>
- pytest.ini에서 필수 테스트 함수에 적용할 marker를 설정해야한다.
import pytest
def test_equal():
assert 3 == 3
@pytest.mark.{marker_name}
def test_not_equal():
assert 3 != 4
$ pytest -m {marker_name}
- marker에 따라 필수가 아닌 테스트 함수들은 deselected로 표시된다.
여기서는 test_not_equal가 deselected이다.
- {marker_name}으로 지정되지 않은 테스트 함수만 실행시키고 싶다면
"not {marker_name}" 을 입력한다. 즉, 테스트 대상 함수를 반전하는 것이다.
- PytestUnknownMarkWarning이 발생한다면,
pytest.ini에 addopts = --strict-markers 을 적용한다.
skip()
- 임시로 특정 테스트 함수를 건너뛰고 싶을 때 사용한다.
- 참고로, 테스트 ‘함수 내’에서 특정 조건에 따라 skip하고 싶다면
pytest.skip(’skip message’)를 사용한다.
- skipped로 표시된다.
parametrize
- 테스트 함수의 매개변수로 인자를 전달하여 테스트를 실행할 수 있다.
- parametrize 데코레이터에 인자를 리스트 따위로 전달한다면
테스트 함수에는 인자가 하나씩 전달되어 한 번씩 실행되므로,
여러 케이스로 테스트를 진행할 수 있게 된다.
- 하나의 케이스에 대해 여러 개의 매개변수를 전달하는 것도 가능하다.
import pytest
_list = ['A', 'B', 'C']
@pytest.mark.parametrize('alpha', _list)
def test_A(alpha):
assert alpha == 'A'
- parametrize 데코레이터의 매개변수가 의미하는 바는
_list의 요소가 차례대로 'alpha'라는 변수명으로,
test_A라는 함수의 매개변수로 전달되어 한 차례씩 실행한다는 것이다.
test_.py::test_A[A] PASSED
test_.py::test_A[B] FAILED
test_.py::test_A[C] FAILED