Pytest 매개 변수

완료됨

pytest의 매개 변수화 기능은 처음에는 복잡해 보일 수 있지만 해결되는 문제를 이해하면 해당 용도는 간단합니다. 기본적으로 매개 변수를 사용하면 다른 입력으로 동일한 테스트 함수를 효율적으로 실행할 수 있으므로 코드가 적은 상세하고 다양한 어설션을 더 쉽게 실행할 수 있습니다.

매개 변수를 호출할 때 첫 번째 인수는 하나 이상의 인수 이름을 포함하는 문자열입니다(예"test\_input_": .). 두 번째 인수에는 인수 값 목록이 포함됩니다(예: ["27", "6+9", "0", "O"].). 마지막 네 개의 인수에는 기본값이 있으며 선택 사항입니다.

여기에서 매개 변수에 대한 영어 pytest API 참조를 찾을 수 있습니다 . pytest. Metafunc.parametrize.

매개 변수를 사용하는 경우

매개 변수를 사용할 수 있는 두 가지 일반적인 시나리오는 다음과 같습니다.

  • 루프 테스트 시
  • 여러 테스트가 동일한 동작을 어설션하는 경우

먼저 매개 변수를 사용하지 않고 각 예제를 검토한 다음 테스트 개선 방법을 보여 드리겠습니다.

For 루프

루프가 있는 테스트 함수의 예는 다음과 같습니다.for

def test_string_is_digit():
    items = ["1", "10", "33"]
    for item in items:
        assert item.isdigit()

이 테스트는 실패할 경우 다음을 비롯한 몇 가지 문제가 발생할 수 있으므로 문제가 됩니다.

  • 모호한 테스트 보고서: 테스트 보고서는 하나의 항목만 실패했는지 또는 여러 오류가 있는지를 명확히 밝히지 않습니다.
  • 단일 테스트 뷰: 모든 항목은 개별 항목 성능을 모호하게 하는 단일 테스트로 표시됩니다.
  • 불확실한 수정 사항: 오류가 수정되면 전체 테스트를 다시 실행하지 않고 모든 문제가 해결되었는지 알 수 있는 방법이 없습니다.

실패할 것으로 예상되는 두 항목을 구체적으로 포함하도록 테스트를 수정해 보겠습니다.

def test_string_is_digit():
    items = ["No", "1", "10", "33", "Yes"]
    for item in items:
        assert item.isdigit()

테스트를 실행하면 해당 목록에 두 개의 잘못된 항목이 있더라도 하나의 오류만 표시됩니다.

$ pytest test_items.py
=================================== FAILURES ===================================
_____________________________ test_string_is_digit _____________________________
test_items.py:4: in test_string_is_digit
    assert item.isdigit()
E   AssertionError: assert False
E    +  where False = <built-in method isdigit of str object at 0x103fa1df0>()
E    +    where <built-in method isdigit of str object at 0x103fa1df0> = 'No'.isdigit
=========================== short test summary info ============================
FAILED test_items.py::test_string_is_digit - AssertionError: assert False
============================== 1 failed in 0.01s ===============================

이는 매개 변수화에 적합한 사용 사례입니다. 테스트를 업데이트하는 방법을 알아보기 전에 루프를 포함하지 for 않는 또 다른 일반적인 상황을 살펴보겠습니다.

동일한 동작을 어설션하는 테스트

동일한 어설션을 만드는 테스트 그룹도 매개 변수화에 적합한 후보입니다. 이전 테스트를 각 항목에 대해 하나의 테스트로 다시 작성한 경우 더 나은 실패 보고를 허용하지만 반복적입니다.

def test_is_digit_1():
    assert "1".isdigit()

def test_is_digit_10():
    assert "10".isdigit()

def test_is_digit_33():
    assert "33".isdigit()

이러한 테스트는 오류가 단일 입력과 쉽게 연결될 수 있다는 점에서 더 좋습니다. 몇 가지 유사한 테스트가 있는 것은 드문 것처럼 보일 수 있지만, 세분화하려고 하는 프로덕션 테스트 도구 모음에서 일반적으로 볼 수 있습니다.

테스트는 실패(또는 통과)를 정확하게 보고할 수 있기 때문에 더 좋을 수 있지만 다음과 같은 문제도 발생합니다.

  • 코드는 반복적이므로 유지 관리 부담이 발생합니다.
  • 테스트를 업데이트할 때 오타와 실수가 발생할 수 있습니다.
  • 반복적이기 때문에 엔지니어는 모든 사용 사례 및 입력을 포함하지 않을 수 있습니다.

매개 변수를 사용하는 방법

이제 매개 변수화에 대한 사용 사례 중 일부를 인식했으므로 실패한 항목이 포함된 루프를 for 사용한 테스트를 업데이트해 보겠습니다.

매개 변수를 사용하려면 라이브러리로 가져온 pytest 다음 함수에서 데코레이터로 사용해야 합니다. 업데이트된 테스트는 다음과 같습니다.

import pytest

@pytest.mark.parametrize("item", ["No", "1", "10", "33", "Yes"])
def test_string_is_digit(item):
    assert item.isdigit()

테스트를 실행하기 전에 변경 내용을 살펴보겠습니다.

데코레이터는 pytest.mark.parametrize() 두 인수를 정의합니다. 첫 번째 인수는 .라는 "item"문자열입니다. 이 문자열은 테스트 함수 정의의 다음 줄에 표시되는 테스트 함수의 명명된 인수 로 사용됩니다. 두 번째 인수는 테스트 값 목록입니다.

풍부한 오류 보고

백그라운드에서 pytest는 해당 목록의 각 항목을 별도의 테스트로 간주합니다. 즉, 테스트 통과 및 실패는 별도로 보고됩니다. 다음을 사용하여 테스트를 pytest실행할 때 어떤 일이 발생하는지 살펴보겠습니다.

$ pytest test_items.py
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /private
collected 5 items

test_items.py F...F                                                      [100%]

=================================== FAILURES ===================================
___________________________ test_string_is_digit[No] ___________________________
test_items.py:5: in test_string_is_digit
    assert item.isdigit()
E   AssertionError: assert False
E    +  where False = <built-in method isdigit of str object at 0x102d45e30>()
E    +    where <built-in method isdigit of str object at 0x102d45e30> = 'No'.isdigit
__________________________ test_string_is_digit[Yes] ___________________________
test_items.py:5: in test_string_is_digit
    assert item.isdigit()
E   AssertionError: assert False
E    +  where False = <built-in method isdigit of str object at 0x102d45df0>()
E    +    where <built-in method isdigit of str object at 0x102d45df0> = 'Yes'.isdigit
=========================== short test summary info ============================
FAILED test_items.py::test_string_is_digit[No] - AssertionError: assert False
FAILED test_items.py::test_string_is_digit[Yes] - AssertionError: assert False
========================= 2 failed, 3 passed in 0.07s ==========================

테스트 보고에는 몇 가지 주목할 만한 항목이 있습니다. 첫째, 단일 테스트 pytest에서 총 5개의 테스트(통과 3회, 실패 2회)를 보고하는 것을 볼 수 있습니다. 오류는 실패한 입력을 포함하여 별도로 보고됩니다.

$ pytest test_items.py
___________________________ test_string_is_digit[No] ___________________________
[...]
E    +    where <built-in method isdigit of str object at 0x102d45e30> = 'No'.isdigit
[...]
FAILED test_items.py::test_string_is_digit[No] - AssertionError: assert False

보고되는 위치가 너무 많아 실패를 일으킨 값을 놓치기 어렵습니다.

자세한 정보 표시 출력 플래그 사용

명령줄에서 테스트가 실행되면 테스트 통과 시 테스트 보고가 최소화됩니다. 테스트를 통해 오류를 수정하는 방법은 다음과 같습니다.

@pytest.mark.parametrize("item", ["0", "1", "10", "33", "9"])
def test_string_is_digit(item):
    assert item.isdigit()

테스트를 실행하면 최소 출력이 생성됩니다.

$ pytest test_items.py 
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /private
collected 5 items

test_items.py .....                                                      [100%]

============================== 5 passed in 0.01s ===============================

세부 정보를 늘리면 매개 변수를 사용할 때 각 테스트에 대해 pytest가 실행되는 값이 표시됩니다.

$ pytest -v test_items.py
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 
rootdir: /private
collected 5 items

test_items.py::test_string_is_digit[0] PASSED                            [ 20%]
test_items.py::test_string_is_digit[1] PASSED                            [ 40%]
test_items.py::test_string_is_digit[10] PASSED                           [ 60%]
test_items.py::test_string_is_digit[33] PASSED                           [ 80%]
test_items.py::test_string_is_digit[9] PASSED                            [100%]

============================== 5 passed in 0.01s ===============================

여러 인수 이름을 사용하는 방법

지금까지 본 예제는 첫 번째 인수에 하나의 인수 이름만 있습니다. 사용 "item" 했지만 문자열에 쉼표로 구분된 첫 번째 인수를 지정하는 여러 인수 이름을 포함할 수 있습니다.

여러 인수 이름을 사용하는 한 가지 사용 사례는 입력 값에 대해 테스트할 예상 값 집합을 전달하려는 경우입니다. 두 번째 인수에서 집합의 각 항목에는 입력 이름 수와 같은 값의 수량이 있어야 합니다. 예를 들어 입력 이름이 있는 경우 두 번째 인수는 "test\_input, expected\_value"다음과 같이 표시될 수 있습니다. [("3+5", 8), ("3*4", 12)]

이 테스트는 Python hasattr() 함수를 사용하여 개체에 특성이 있는지 확인합니다. 개체에 연결된 특성이 있는지 여부에 따라 부울을 반환합니다.

>>> hasattr(dict(), "keys")
True
>>> hasattr("string", "append")
False

hasattr() 두 개의 인수가 필요하므로 다음과 같은 방법으로 매개 변수를 사용할 수 있습니다.

@pytest.mark.parametrize("item, attribute", [("", "format"), (list(), "append")])
def test_attributes(item, attribute):
    assert hasattr(item, attribute)

매개 변수 데코레이터는 여전히 첫 번째 인수에 단일 문자열을 사용하지만 두 인수 이름은 쉼표로 구분되어 테스트 함수의 인수가 됩니다. 이 경우는 item 다음과 같습니다 attribute.

다음은 두 쌍의 항목 목록입니다. 이러한 각 쌍은 item 테스트할 쌍과 해당 쌍을 attribute 나타냅니다.

pytest가 전달되는 개체의 문자열 표현을 작성할 수 없는 경우 이를 만듭니다. 테스트를 실행할 때 이를 확인할 수 있습니다.

$ pytest -v test_items.py
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 
rootdir: /private
collected 2 items

test_items.py::test_attributes[-format] PASSED                           [ 50%]
test_items.py::test_attributes[item1-append] PASSED                      [100%]

============================== 2 passed in 0.01s ===============================