Pytest 해보기

Pytest 해보기

Pytest

Pytest란 무엇인가? python을 테스트하는 프레임워크

본격적인 개발에 들어가기 전에 테스트 계획 및 코드를 작성

소규모 개발 중에 일어난 오류, 에러는 금방 알아차릴 수 있으나. 대규모의 프로젝트로 가면 원인 찾는것도 리소스 사용하기에 테스트 기반의 개발을 한다고 합니다.

블로그 글을 보고 Pytest를 알아본 내용입니다.

저는 디버깅용으로 사용했기에, assert를 사용하였습니다.

만약 실서비스 환경에서 필요하다면, pytest.raises를 사용해야 합니다.


1. Pytest 기본


설치와 기본적인 pytest를 진행해보겠습니다.

  • log-cli-level=DEBUG를 해주어야 로깅 시 정보가 출력됩니다.
  • Print 출력 값을 보고 싶다면 capture 옵션을 주어야 합니다.
  • assert 는 에러를 발생시키는 함수로, False, None, 0의 값이라면 에러가 발생합니다.
1
2
3
$ pip install pytest
$ pytest --log-cli-level=DEBUG file_name.py
$ pytest --log-cli-level=DEBUG --capture=tee-sys file_name.py
1
2
3
4
5
6
7
8
# basic_pytest.py
import pytest
import logging
import sys

def test_function_01():
    logging.info(sys._getframe(0).f_code.co_name)
    assert True

Untitled-32


2. 특정 테스팅만 실행 + 처리 과정 알아보기


특정 테스트만 실행하기

  • 특정 테스팅만 실행하고 싶다면, Decorator 함수를 활용해야 합니다. @pytest.mark.mark_name
  • pytest.ini 파일을 만들어 이름을 미리 지정해 줍니다. 마커이름 : 설명입니다.
  • 마커 이름은 dicttests, kai 입니다.
  • -m 옵션을 주어 원하는 마커만 실행시킬 수 있습니다.
1
2
3
4
5
# pytest.ini
[pytest]
markers =
    dicttests: marks tests
    kai: test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# sample_pytest.py
import pytest
import logging
import sys

def setup_function(function):
    logging.info(sys._getframe(0).f_code.co_name)
    logging.warning(sys._getframe(0).f_code.co_name)
    logging.error(sys._getframe(0).f_code.co_name)
    logging.critical(sys._getframe(0).f_code.co_name)
    print('hello-world')

def teardown_function(function):
    logging.info(sys._getframe(0).f_code.co_name)

@pytest.mark.dicttests
def test_function_01():
    logging.info(sys._getframe(0).f_code.co_name)
    assert True

@pytest.mark.kai
def test_function_02():
    logging.info(sys._getframe(0).f_code.co_name)
    assert False, "문제가 발생했어요 function02를 확인해보세요"
1
$ pytest --log-cli-level=DEBUG sample_pytest.py -m dicttests

Untitled-33

동작 원리

  • pytest의 기본적인 로직이 testcase 함수 호출 시 앞 뒤로 아래와 같이 호출합니다.
    • 실행 전에 setup_function
    • 실행 후에는 teardown_function(function)
  • 각 로그가 잘 찍히는지 확인하기 위해서 info, warning, error, critical을 전부 찍어보았습니다.
1
$ pytest --log-cli-level=DEBUG sample_pytest.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# sample_pytest.py
import pytest
import logging
import sys

def setup_function(function):
    logging.info(sys._getframe(0).f_code.co_name)
    logging.warning(sys._getframe(0).f_code.co_name)
    logging.error(sys._getframe(0).f_code.co_name)
    logging.critical(sys._getframe(0).f_code.co_name)
    print('hello-world')

def teardown_function(function):
    logging.info(sys._getframe(0).f_code.co_name)

@pytest.mark.dicttests
def test_function_01():
    logging.info(sys._getframe(0).f_code.co_name)
    assert True

@pytest.mark.kai
def test_function_02():
    logging.info(sys._getframe(0).f_code.co_name)
    assert False, "문제가 발생했어요 function02를 확인해보세요"

Untitled-34


3. 클래스를 활용하여 테스트 케이스 구현


  • class로 구현 시에는 setup_class와 teardown_class에 @classmethod 데코레이터를 사용해합니다.
  • 구현 방식은 2번에서 봤던 것처럼 method 호출 시 setup_class, teardown_class를 호출한 것과 같이 class가 제일 큰 껍데기이니 처음 마지막을 담당하고 메소드 별로 setup_method, teardown_method를 호출 할 것이라고 생각을 할 수 있습니다.
  • @pytest.mark.skip 을 통하여 테스트를 건너띌 수 있습니다. 간단한 예시이니 직접 해보시는걸 추천합니다.
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
import logging
import pytest
from time import sleep

class TestClassSample():
    @classmethod
    def setup_class(cls):
        logging.info('setup_class')

    @classmethod
    def teardown_class(cls):
        logging.info('teardown_class')
        pass

    def setup_method(self, method):
        logging.info('setup_method')

    def teardown_method(self, method):
        logging.info('teardown_method')

    @pytest.mark.markers
    def test_01(self):
        logging.info('test01')
        sleep(1)
        assert True

    @pytest.mark.skip(reason="Skip reason")
    def test_02(self):
        logging.info('test02')
        sleep(1)
        assert True

    def test_03(self):
        logging.info('test03')
        sleep(1)
        assert False, "line 37번에서 문제가 발생했어요"

Untitled-35