포스트

Pytest Basics

들어가며

기존에 테스트 코드 작성 경험이 많지 않았습니다. 기존에 Java의 JUnit과 Node.js의 Jest를 조금씩 사용해봤지만, 잘 이해하고 사용한 것 같다는 느낌이 들지 않았습니다. 이번에 회사에서 작업을 하며 단위 테스트를 작성할 필요가 있었는데 폭풍검색과 기존 코드 컨텍스트를 보며 작성은 했지만 보다 잘 다루고 싶다는 생각이 들어 Pytest에 관하여 TIL을 작성해보고자 합니다.

Pytest란?

말 그대로 py(thon)을 test하는 테스팅 프레임워크입니다. pytest 공식 홈페이지에서는 다음과 같이 설명합니다.

pytest: helps you write better programs

Pytest는 직관적이고 강력한 기능으로 인해 Python 개발자들 사이에서 널리 사용됩니다.

Pytest 특징

문법

Pytest는 Python의 내장 assert 문을 활용하여 테스트를 수행합니다. 이 덕분에 별도의 테스트 문법을 학습할 필요가 없고, Python 코드처럼 직관적으로 작성할 수 있습니다.

  • 예시
    1
    2
    
    def test_sum():
    		assert sum([1,2,3]) == 6, "Should be 6"
    

풍부한 플러그인과 확장성

Pytest는 커뮤니티에서 제공하는 다양한 플러그인과 함께 사용할 수 있습니다. 예를 들어, 병렬 실행을 지원하는 pytest-xdist 플러그인이나 코드 커버리지를 확인하는 pytest-cov 플러그인이 있습니다.

함수 테스팅

Pytest는 클래스 기반 테스팅뿐만 아니라 함수 기반 테스팅도 지원합니다. 함수 기반 테스팅은 간단한 테스트 작성에 유리하며, 단순하고 읽기 쉬운 테스트 코드를 작성할 수 있게 도와줍니다.

병렬 테스팅

Pytest는 여러 테스트를 병렬로 실행할 수 있어 테스트 속도를 크게 향상시킬 수 있습니다. 이를 위해 pytest-xdist 플러그인을 사용할 수 있습니다.

데코레이터를 활용한 유연성

Pytest는 데코레이터를 통해 테스트의 유연성과 가독성을 높입니다. 데코레이터를 사용하면 특정 조건에서만 테스트를 실행하거나, 반복 테스트를 설정할 수 있습니다.

  • 주요 데코레이터 예시
    • @pytest.mark.skip: 특정 조건에서 테스트를 건너뜁니다.
    • @pytest.mark.parametrize: 여러 입력값을 사용하여 동일한 테스트를 반복 실행합니다.
    • @pytest.mark.xxx pytest -m xxx -v 와 같은 명령어를 통해 특정 테스트들만 수행시킬 수 있습니다.
1
2
3
4
5
6
7
8
9
import pytest

@pytest.mark.skip(reason="테스트 건너뛰기 예제")
def test_skip():
    assert 1 == 2

@pytest.mark.parametrize("input,expected", [(1, 2), (3, 4), (5, 6)])
def test_parametrize(input, expected):
    assert input + 1 == expected

테스트 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@pytest.mark.parametrize(
    ["given_url", "given_response", "given_status", "expected_code"],
    [
        (
            "http://endpoint_url/200",
            {
                "message": "You don't have permission to operate advertiser 1050770154406178437.",
                "code": 40001,
                "data": [],
                "request_id": "202209152307510102452V203X0BADF672",
            },
            200,
            40001,
        ),
        (
            "http://endpoint_url/200",
            {
                "code": 40105,
                "message": "Access token is invalid, when you call access_token or refresh_token API, old token will become invalid.",
                "request_id": "20230907030651D950B54FA48D6B200526",
                "data": {},
            },
            200,
            40105,
        ),
    ],
)
@responses.activate
def test_request_invalid_token_exception(
    given_url, given_response, given_status, expected_code
):
    responses.add(
        responses.GET,
        given_url,
        json=given_response,
        status=given_status,
    )

    with pytest.raises(InvalidTokenException) as e:
        RequestWrapper(backoff_factor=0).request(
            method="GET",
            endpoint=given_url,
        )

    response = json.loads(e.value.response)
    assert expected_code == response["code"]
    assert given_response.get("message") == response["message"]

    assert e.value.status_code == given_status
    assert e.value.headers is not None

  • @pytest.mark.parametrize

앞서 설명한 위 데코레이터를 통해 동일한 테스트를 여러 입력값으로 반복 실행할 수 있도록 해줍니다. 의도적으로 에러가 나는 값을 입력값으로 넣었습니다.

  • @responses.activate

이 데코레이터는 responses 라이브러리를 활성화하여 HTTP 요청을 Mock합니다. 실제 네트워크 호출 없이 @pytest.mark.parametrize로 미리 정의한 응답을 반환받을 수 있습니다.

  • pytest.raises

특정 예외가 발생하는지 확인합니다.

  • 이후 반환된 에러 코드와 메시지가 일치하는지 확인합니다.

참고자료

https://docs.pytest.org/en/stable/

https://github.com/pytest-dev/pytest

https://www.youtube.com/watch?v=yA5jqNCCOLE&list=PLL34mf651faNqwhZEM91zU3c-dcc4dLF0

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.