Etc Programming
FastAPI
Pytest
Get Started

Pytest 시작하기

pytest 설치

pytest는 Python 3.8+ 또는 PyPy3가 필요합니다.

명령줄에서 다음 명령을 실행하세요:

pip install -U pytest

올바른 버전이 설치되었는지 확인:

$ pytest --version
pytest 8.4.1

첫 번째 테스트 작성

test_sample.py라는 새 파일을 만들고, 함수와 테스트를 포함시키세요:

# test_sample.py 내용
def func(x):
    return x + 1
 
 
def test_answer():
    assert func(3) == 5

테스트 실행:

$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
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:6: AssertionError
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 4 == 5
============================ 1 failed in 0.12s =============================

[100%]는 모든 테스트 케이스 실행의 전체 진행률을 나타냅니다. 완료 후, func(3)이 5를 반환하지 않기 때문에 pytest는 실패 보고서를 보여줍니다.

참고

assert 문을 사용하여 테스트 기대치를 검증할 수 있습니다. pytest의 고급 assertion 분석 기능은 assert 표현식의 중간 값을 지능적으로 보고하므로 JUnit 레거시 메서드의 많은 이름을 피할 수 있습니다.

여러 테스트 실행

pytest는 현재 디렉토리와 하위 디렉토리에서 test_*.py 또는 *_test.py 형태의 모든 파일을 실행합니다. 더 일반적으로는 표준 테스트 검색 규칙을 따릅니다.

특정 예외가 발생하는지 검증

raises 헬퍼를 사용하여 코드가 예외를 발생시키는지 검증:

# test_sysexit.py 내용
import pytest
 
 
def f():
    raise SystemExit(1)
 
 
def test_mytest():
    with pytest.raises(SystemExit):
        f()

"quiet" 보고 모드로 테스트 함수 실행:

$ pytest -q test_sysexit.py
.                                                                    [100%]
1 passed in 0.12s

참고

-q/--quiet 플래그는 이 예제와 다음 예제들에서 출력을 간략하게 유지합니다.

예상되는 예외에 대한 더 자세한 지정은 예상 예외에 대한 Assertions (opens in a new tab)를 참조하세요.

클래스에서 여러 테스트 그룹화

여러 테스트를 개발하면 클래스로 그룹화하고 싶을 수 있습니다. pytest를 사용하면 하나 이상의 테스트를 포함하는 클래스를 쉽게 만들 수 있습니다:

# test_class.py 내용
class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x
 
    def test_two(self):
        x = "hello"
        assert hasattr(x, "check")

pytest는 Python~~ 테스트 검색 규칙에 따라 모든 테스트를 검색하므로 ~~test_ 접두사가 있는 함수를 모두 찾습니다. 어떤 것도 서브클래스할 필요가 없지만, 클래스에 Test 접두사를 붙여야 합니다. 그렇지 않으면 클래스가 건너뛰어집니다. 파일명을 전달하여 모듈을 간단히 실행할 수 있습니다:

$ pytest -q test_class.py
.F                                                                   [100%]
================================= FAILURES =================================
____________________________ TestClass.test_two ____________________________
 
self = <test_class.TestClass object at 0xdeadbeef0001>
 
    def test_two(self):
        x = "hello"
>       assert hasattr(x, "check")
E       AssertionError: assert False
E        +  where False = hasattr('hello', 'check')
 
test_class.py:8: AssertionError
========================= short test summary info ==========================
FAILED test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.12s

첫 번째 테스트는 통과했고 두 번째는 실패했습니다. assertion의 중간 값을 쉽게 볼 수 있어 실패 이유를 이해하는 데 도움이 됩니다.

클래스에서 테스트를 그룹화하는 것은 다음과 같은 이유로 유익할 수 있습니다:

  • 테스트 조직화
  • 특정 클래스의 테스트만을 위한 픽스처 공유
  • 클래스 레벨에서 마크 적용 및 모든 테스트에 암시적으로 적용

클래스 내에서 테스트를 그룹화할 때 알아야 할 점은 각 테스트가 클래스의 고유한 인스턴스를 가진다는 것입니다. 각 테스트가 동일한 클래스 인스턴스를 공유하는 것은 테스트 격리에 매우 해로우며 나쁜 테스트 관행을 조장할 것입니다. 이는 아래에 설명되어 있습니다:

# test_class_demo.py 내용
class TestClassDemoInstance:
    value = 0
 
    def test_one(self):
        self.value = 1
        assert self.value == 1
 
    def test_two(self):
        assert self.value == 1
$ pytest -k TestClassDemoInstance -q
.F                                                                   [100%]
================================= FAILURES =================================
______________________ TestClassDemoInstance.test_two ______________________
 
self = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002>
 
    def test_two(self):
>       assert self.value == 1
E       assert 0 == 1
E        +  where 0 = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002>.value
 
test_class_demo.py:9: AssertionError
========================= short test summary info ==========================
FAILED test_class_demo.py::TestClassDemoInstance::test_two - assert 0 == 1
1 failed, 1 passed in 0.12s

클래스 레벨에서 추가된 속성은 클래스 속성이므로 테스트 간에 공유됩니다.

기능 테스트를 위한 고유 임시 디렉토리 요청

pytest는 고유 임시 디렉토리와 같은 임의의 리소스를 요청하기 위한 내장 픽스처/함수 인수를 제공합니다:

# test_tmp_path.py 내용
def test_needsfiles(tmp_path):
    print(tmp_path)
    assert 0

테스트 함수 시그니처에 tmp_path 이름을 나열하면 pytest가 테스트 함수 호출을 수행하기 전에 픽스처 팩토리를 찾아 호출하여 리소스를 생성합니다. 테스트가 실행되기 전에 pytest는 테스트 호출마다 고유한 임시 디렉토리를 생성합니다:

$ pytest -q test_tmp_path.py
F                                                                    [100%]
================================= FAILURES =================================
_____________________________ test_needsfiles ______________________________
 
tmp_path = PosixPath('PYTEST_TMPDIR/test_needsfiles0')
 
    def test_needsfiles(tmp_path):
        print(tmp_path)
>       assert 0
E       assert 0
 
test_tmp_path.py:3: AssertionError
--------------------------- Captured stdout call ---------------------------
PYTEST_TMPDIR/test_needsfiles0
========================= short test summary info ==========================
FAILED test_tmp_path.py::test_needsfiles - assert 0
1 failed in 0.12s

임시 디렉토리 처리에 대한 자세한 정보는 임시 디렉토리와 파일 (opens in a new tab)에서 확인할 수 있습니다.

다음 명령으로 어떤 종류의 내장 pytest 픽스처가 존재하는지 확인하세요:

pytest --fixtures   # 내장 및 사용자 정의 픽스처 표시

이 명령은 -v 옵션이 추가되지 않는 한 선행 _가 있는 픽스처는 생략합니다.

계속 읽기

고유한 워크플로에 맞게 테스트를 사용자 정의하는 데 도움이 되는 추가 pytest 리소스를 확인하세요: