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