Pytest

newhyork·2022년 7월 2일
0

Pytest


  • Python test framework 중 하나이다.
  • 관련하여 다양한 plug-in이 있다.

unit test


  • 단위 테스트의 단위는, 함수 하나에 대하여 그리고 하나의 케이스에 대한 것이다.
  • 테스트 함수 간에는 변수나 객체를 공유하지 않는 것이 원칙이다.

usage

# my_math.py 
def average(num_list):
    return sum(num_list) / len(num_list)


# test_my_math.py
from my_math import *

def test_average():
    assert average([1,2,3]) == 2
- 함수 명은 'test_'로 시작하도록 한다. 


# command line
$ 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

# calculator.py
class Calculator:

    def add(self, x, y):
        return x + y

    def subtract(self, x, y):
        return x - y


# test_calculator.py
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도 가능하다.
# test_.py
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 result
test_.py::test_
set up
PASSED
tear down
- print()의 output까지 출력하기 위해, -s 옵션을 추가한다. 

@pytest.mark


  • 테스트 함수에 metadata를 설정할 수 있다.

{marker_name}

  • 필수 테스트 함수만 실행하고자 할 때 사용한다.
# pytest.ini
markers = 
    <marker_name>: <comment>
- pytest.ini에서 필수 테스트 함수에 적용할 marker를 설정해야한다.


# test_.py
import pytest

def test_equal():
    assert 3 == 3

@pytest.mark.{marker_name}
def test_not_equal():
    assert 3 != 4


# command line
$ 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 데코레이터에 인자를 리스트 따위로 전달한다면
    테스트 함수에는 인자가 하나씩 전달되어 한 번씩 실행되므로,
    여러 케이스로 테스트를 진행할 수 있게 된다.
    • 하나의 케이스에 대해 여러 개의 매개변수를 전달하는 것도 가능하다.
# test_.py
import pytest

_list = ['A', 'B', 'C']

@pytest.mark.parametrize('alpha', _list)
def test_A(alpha):
    assert alpha == 'A'
- parametrize 데코레이터의 매개변수가 의미하는 바는 
  _list의 요소가 차례대로 'alpha'라는 변수명으로,
  test_A라는 함수의 매개변수로 전달되어 한 차례씩 실행한다는 것이다. 


# test result
test_.py::test_A[A] PASSED
test_.py::test_A[B] FAILED
test_.py::test_A[C] FAILED

0개의 댓글