Pytest 기본 사항

완료됨

Pytest로 테스트를 시작하겠습니다. 이전 단원에서 언급했듯이 Pytest는 구성 가능성이 매우 뛰어나 복잡한 테스트 도구 모음을 처리할 수 있습니다. 그러나 테스트 작성을 시작하는 것은 어렵지 않습니다. 실제로 프레임워크를 사용하면 테스트를 더 쉽게 작성할 수 있습니다.

이 섹션의 끝부분에는 첫 번째 테스트 작성을 시작하고 Pytest를 사용하여 실행하는 데 필요한 모든 것이 있어야 합니다.

규칙

테스트를 작성하기 전에 Pytest가 사용하는 몇 가지 테스트 규칙을 다루어야 합니다.

Python에서는 테스트 파일, 테스트 디렉터리 또는 일반 테스트 레이아웃에 대한 어려운 규칙이 없습니다. 이러한 규칙을 알고 있으면 추가 구성 없이 자동 테스트 검색 및 실행을 활용할 수 있습니다.

디렉터리 및 테스트 파일 테스트

테스트의 주 디렉터리가 테스트 디렉터리입니다. 이 디렉터리를 프로젝트의 루트 수준에 배치할 수 있지만 코드 모듈과 함께 보는 것도 드문 일이 아닙니다.

참고 항목

이 모듈에서는 기본적으로 프로젝트의 루트에서 테스트를 사용합니다.

작은 Python 프로젝트인 jformat의 루트가 어떻게 보이는지 살펴보겠습니다.

.
├── README.md
├── jformat
│   ├── __init__.py
│   └── main.py
├── setup.py
└── tests
    └── test_main.py

테스트 디렉터리가 단일 테스트 파일이 있는 프로젝트의 루트에 있습니다. 이 경우 테스트 파일을 test_main.py라고 합니다. 이 예에서는 두 가지 중요한 규칙을 보여 줍니다.

  • tests 디렉터리를 사용하여 테스트 파일과 중첩된 테스트 디렉터리를 배치합니다.
  • 테스트가 포함된 접두사 테스트 파일입니다. 접두사는 파일에 테스트 코드가 포함되어 있음을 나타냅니다.

주의

디렉터리 이름으로 test(단수 형식)를 사용하지 마세요. test 이름은 Python 모듈이므로 동일한 디렉터리를 만들면 재정의됩니다. 대신 항상 복수형 tests를 사용합니다.

테스트 함수

Pytest를 사용하는 강력한 이유 중 하나는 테스트 함수를 작성할 수 있다는 것입니다. 테스트 파일과 마찬가지로 테스트 함수는 test_로 시작해야 합니다. test_ 접두사는 Pytest가 테스트를 수집하고 실행하도록 보장합니다.

간단한 테스트 함수는 다음과 같습니다.

def test_main():
    assert "a string value" == "a string value"

참고 항목

unittest에 대해 잘 알고 있다면 테스트 함수에서 assert 사용을 보는 것은 놀라운 일이 될 수 있습니다. 나중에 일반 어설션을 좀 더 자세히 다루겠지만, Pytest를 사용하면 일반 어설션으로 자세한 실패 보고를 얻을 수 있습니다.

테스트 클래스 및 테스트 메서드

파일과 함수의 규칙과 유사하게 테스트 클래스와 테스트 메서드는 다음 규칙을 사용합니다.

  • 테스트 클래스의 접두사는 Test
  • 테스트 메서드의 접두사는 test_

Python의 unittest 라이브러리와 핵심적인 차이점은 상속이 필요 없다는 것입니다.

다음 예에서는 클래스와 메서드에 이러한 접두사와 기타 Python 명명 규칙을 사용합니다. 이는 애플리케이션에서 사용자 이름을 확인하는 작은 테스트 클래스를 보여 줍니다.

class TestUser:

    def test_username(self):
        assert default() == "default username"

테스트 실행

Pytest는 테스트 프레임워크이자 테스트 실행기입니다. 테스트 실행기는 명령줄에서 상위 수준에서 다음을 수행할 수 있는 실행 파일입니다.

  • 테스트 실행에 대한 모든 테스트 파일, 테스트 클래스 및 테스트 함수를 찾아 테스트 수집 수행
  • 모든 테스트를 실행하여 테스트 실행을 시작합니다.
  • 실패, 오류 및 테스트 통과 추적을 유지합니다.
  • 테스트 실행이 끝날 때 자세한 보고를 제공합니다.

참고 항목

Pytest는 외부 라이브러리이므로 사용하려면 설치해야 합니다.

test_main.py 파일에서 이러한 내용을 고려할 때 테스트를 실행하는 Pytest의 동작 방식을 확인할 수 있습니다.

# contents of test_main.py file

def test_main():
    assert True

명령줄에서 test_main.py 파일이 있는 동일한 경로에서 pytest 실행 파일을 실행할 수 있습니다.

 $ pytest
=========================== test session starts ============================
platform -- Python 3.10.1, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /private/tmp/project
collected 1 item

test_main.py .                                                       [100%]

============================ 1 passed in 0.00s =============================

백그라운드에서 Pytest는 구성 없이 테스트 파일에서 테스트 예를 수집합니다.

강력한 assert 문

지금까지 우리의 테스트 예는 모두 일반 assert 호출을 사용하고 있습니다. 일반적으로 Python에서 assert 문은 어설션이 실패할 때 적절한 보고가 부족하기 때문에 테스트에 사용되지 않습니다. 하지만 Pytest에는 이런 제한 사항이 없습니다. 백그라운드에서 Pytest는 사용자가 더 많은 코드를 작성하거나 아무 것도 구성하지 않고도 문이 풍부한 비교를 수행할 수 있도록 합니다.

일반 assert 문을 사용하면 Python의 연산자를 사용할 수 있습니다. 예: >, <, !=, >= 또는 <=. 모든 Python 연산자는 유효합니다. 이 기능은 Pytest의 가장 중요한 단일 기능일 수 있습니다. 어설션을 작성하는 새 구문을 학습할 필요가 없습니다.

Python 개체와의 일반적인 비교를 처리할 때 변환하는 방법을 살펴보겠습니다. 이 경우 긴 문자열을 비교할 때의 실패 보고서를 살펴보겠습니다.

================================= FAILURES =================================
____________________________ test_long_strings _____________________________

    def test_long_strings():
        left = "this is a very long strings to be compared with another long string"
        right = "This is a very long string to be compared with another long string"
>       assert left == right
E       AssertionError: assert 'this is a ve...r long string' == 'This is a ve...r long string'
E         - This is a very long string to be compared with another long string
E         ? ^
E         + this is a very long strings to be compared with another long string
E         ? ^                         +

test_main.py:4: AssertionError

Pytest는 실패에 대한 유용한 컨텍스트를 보여 줍니다. 문자열의 시작 부분에 잘못된 대/소문자가 있거나 단어에 추가 문자가 있습니다. 그러나 문자열 외에도 Pytest는 다른 개체 및 데이터 구조에 도움이 될 수 있습니다. 예를 들어, 목록에서는 다음과 같이 동작합니다.

________________________________ test_lists ________________________________

    def test_lists():
        left = ["sugar", "wheat", "coffee", "salt", "water", "milk"]
        right = ["sugar", "coffee", "wheat", "salt", "water", "milk"]
>       assert left == right
E       AssertionError: assert ['sugar', 'wh...ater', 'milk'] == ['sugar', 'co...ater', 'milk']
E         At index 1 diff: 'wheat' != 'coffee'
E         Full diff:
E         - ['sugar', 'coffee', 'wheat', 'salt', 'water', 'milk']
E         ?                     ---------
E         + ['sugar', 'wheat', 'coffee', 'salt', 'water', 'milk']
E         ?           +++++++++

test_main.py:9: AssertionError

이 보고서는 인덱스 1(목록의 두 번째 항목)이 다르다는 것을 식별합니다. 이는 인덱스 번호를 식별할 뿐만 아니라 실패에 대한 표현도 제공합니다. 항목 비교 외에도 항목이 누락되었는지 보고하고 정확히 어떤 항목이 있는지 알려줄 수 있는 정보를 제공할 수도 있습니다. 다음 경우에 "milk"입니다.

________________________________ test_lists ________________________________

    def test_lists():
        left = ["sugar", "wheat", "coffee", "salt", "water", "milk"]
        right = ["sugar", "wheat", "salt", "water", "milk"]
>       assert left == right
E       AssertionError: assert ['sugar', 'wh...ater', 'milk'] == ['sugar', 'wh...ater', 'milk']
E         At index 2 diff: 'coffee' != 'salt'
E         Left contains one more item: 'milk'
E         Full diff:
E         - ['sugar', 'wheat', 'salt', 'water', 'milk']
E         + ['sugar', 'wheat', 'coffee', 'salt', 'water', 'milk']
E         ?                    ++++++++++

test_main.py:9: AssertionError

마지막으로 사전을 사용하여 동작하는 방법을 살펴보겠습니다. 오류가 있는 경우 두 개의 큰 사전을 비교하는 것이 어려울 수 있지만 Pytest는 컨텍스트를 제공하고 오류를 정확히 파악하는 데 뛰어난 작업을 수행합니다.

____________________________ test_dictionaries _____________________________

    def test_dictionaries():
        left = {"street": "Ferry Ln.", "number": 39, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
        right = {"street": "Ferry Lane", "number": 38, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
>       assert left == right
E       AssertionError: assert {'county': 'F...rry Ln.', ...} == {'county': 'F...ry Lane', ...}
E         Omitting 3 identical items, use -vv to show
E         Differing items:
E         {'street': 'Ferry Ln.'} != {'street': 'Ferry Lane'}
E         {'number': 39} != {'number': 38}
E         Full diff:
E           {
E            'county': 'Frett',...
E
E         ...Full output truncated (12 lines hidden), use '-vv' to show

이 테스트에서는 사전에 두 가지 오류가 있습니다. 하나는 "street" 값이 다르다는 것이고 다른 하나는 "number" 값이 일치하지 않는다는 것입니다.

Pytest는 이러한 차이점을 정확하게 감지합니다(단일 테스트에서 한 번의 실패임에도 불구하고). 사전에 많은 항목이 포함되어 있으므로 Pytest는 동일한 부분을 생략하고 관련 콘텐츠만 표시합니다. 제안된 -vv 플래그를 사용하여 출력의 세부 정보를 늘리면 어떻게 되는지 살펴보겠습니다.

____________________________ test_dictionaries _____________________________

    def test_dictionaries():
        left = {"street": "Ferry Ln.", "number": 39, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
        right = {"street": "Ferry Lane", "number": 38, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
>       assert left == right
E       AssertionError: assert {'county': 'Frett',\n 'number': 39,\n 'state': 'Nevada',\n 'street': 'Ferry Ln.',\n 'zipcode': 30877} == {'county': 'Frett',\n 'number': 38,\n 'state': 'Nevada',\n 'street': 'Ferry Lane',\n 'zipcode': 30877}
E         Common items:
E         {'county': 'Frett', 'state': 'Nevada', 'zipcode': 30877}
E         Differing items:
E         {'number': 39} != {'number': 38}
E         {'street': 'Ferry Ln.'} != {'street': 'Ferry Lane'}
E         Full diff:
E           {
E            'county': 'Frett',
E         -  'number': 38,
E         ?             ^
E         +  'number': 39,
E         ?             ^
E            'state': 'Nevada',
E         -  'street': 'Ferry Lane',
E         ?                    - ^
E         +  'street': 'Ferry Ln.',
E         ?                     ^
E            'zipcode': 30877,
E           }

pytest -vv 실행하면 보고에서 세부 정보가 늘어나고 세부적인 비교가 제공됩니다. 이 보고서는 오류를 검색하고 보여줄 뿐만 아니라, 문제를 수정하기 위해 신속하게 변경할 수 있도록 도와줍니다.