본문 바로가기
파이썬

[Python] pytest를 통한 파이썬 테스트 (feat. fixture)

by 책 읽는 개발자_테드 2022. 4. 20.
반응형

진지한 파이썬 를 읽고, 정리한 글입니다.

목차

· pytest란?

· 테스트 건너뛰기

· 특정 테스트 실행하기

· 병렬로 테스트 실행하기

· fixture로 테스트에 사용하는 객체 작성하기

· 테스트 시나리오 실행하기


pytest란?


· 간단한 사용이 있는 소규모 프로젝트의 경우, pytest 패키지는 pip를 통해 설치되면 pytest 명령을 제공한다.

· pytest 명령어는 이름이 test_로 시작하는 모든 파일을 로드하고, test_로 시작되는 모든 기능을 실행한다.

 

1. pytest를 설치한다.

pip install pytest

 

2. 소스트리에  테스트가 통과하는 간단한 테스트를 작성한다.

 

test_true.py

def test_true():
    assert True

 

3. pytest를 실행한다.

pytest -v test_true.py

 

4. test_true.py에 실패하는 테스트를 추가한다.

def test_false():
    assert False

 

5. 다시 pytest를 실행한다.

pytest -v test_true.py

- AssertionError 예외가 발생하며 테스트가 실패한다.

- assert 테스트는 인수가 거짓(False, None, 0 등)으로 평가될때 AssertionError가 발생한다.

 

테스트 건너뛰기


· 어떤 테스트를 실행할 수 없는 경우 해당 테스트를 건너뛸 수 있다.

- pytest.skip() 함수를 사용하여 테스트를 건너뛴 것으로 표시하고 다음 함수로 이동할 수 있다.

- pytest.mark.skip 데커레이터는 테스트 함수를 무조건 건머뛴다. 따라서 테스트를 항상 건너뛰어야 할 때 사용한다.

import pytest

try:
    import mylib
except ImportError:
    mylib = None

@pytest.mark.skip("Do not run this")
def test_fail():
    assert False

@pytest.mark.skipif(mylib is None, reason="mylib is not available")
def test_mylib():
    assert mylib.foobar() == 42

def test_skip_at_runtime():
    if True:
        pytest.skip("Finally I don't want to run it")

결과

 

특정 테스트 실행하기


· pytest 명령줄에 인수로 해당 디렉터리 또는 파일을 전달하여 실행할 테스트를 선택할 수 있다. 즉, 테스트의 특정 부분집합만 실행할 수 있다.

· pytest는 디렉터리 또는 test_로 시작하는 파일을 인수로 받아들인다. 디렉터리를 인수로 받으면, 디렉터리를 재귀적으로 스캔하여 test_*.py 패턴과 일치하는 파일을 실행한다.

· 이름과 일치하는 테스트만 실행하기 위해 명령줄에 -k 인수가 있는 필터를 추가할 수 있다.

pytest -v test_skip_exam.py -k test_fail

 

· pytest는 필터로 사용할 수 있는 키워드로 테스트를 표시할 수 있는 동적 마킹 시스템을 제공한다. 

- 이를 통해 개발자는 기능이나 유형별로 테스트를 그룹활할 수 있다.

· -m 옵션을 사용하여 이러한 방식으로 테스트를 표시할 수 있다.

 

test_mark.py

import pytest

@pytest.mark.dicttest
def test_something():
    a = ['a', 'b']
    assert a == a

def test_something_else():
    assert False
    

다음과 같이 pytest에 -m 옵션를 사용하여 두 개의 테스트 중 하나만 실행할 수 있다.

$ pytest -v test_mark.py -m dicttest

 

-m 옵션을 통해 표시되지 않은 테스트를 실행할 수도 있다.

$ pytest -v test_mark.py -m "not dicttest"

 

TODO: 이외에도 pytest는 not, and, or 키워드로 구성된 복합 수식(complex expression)을 수행하여 떠 고급 필터링을 할 수 있다.

 

병렬로 테스트 실행하기


· 기본적으로 pytest는 정의되지 않은 순서로 연속해서 모든 테스트를 실행한다. 하지만, 테스트 도구 모음을 실행하는 데 시간이 오래 걸릴 수 있다. 대부분의 컴퓨터에는 여러 CPU가 있으므로 테스트를 목록을 분할하고 여러 CPU에서 실행하면 속도를 높일 수 있다.

· pytest-xdist 플러그인으로 이러한 방법을 사용할 수 있다. 

- 이 플러그인은 pytest 명령줄을 --numprocesses 인수(-n으로 단축)로 확장하며, 이 인수는 CPU의 개수를 인수로 허용한다.

ex) "pytest -n 4"를 실행하면, 병렬 프로세스를 사용하여 테스트 도구 모음을 실행하고 CPU 간의 부하를 분산시킨다.

- CPU 개수는 컴퓨터마다 다르므로, 자동 키워드 값을 허용한다. 이 경우 컴퓨터를 조사하여 사용 가능한 CPU 개수를 검색하고, 그 수만큼 프로세스를 시작한다.

 

· 사용법:

1. 설치하기

$ pip install pytest-xdist

2. "-n 원하는프로세스수" 인수를 포함하여 원하는 개수 만큼 프로세스가 생성되도록 한다.

$ pip install pytest-xdist -n 3

 

3. "-n auto" 인수를 포함하여 CPU 개수 만큼 자동으로 프로세스가 생성되도록 한다.

$ pip install pytest-xdist -n 3

 

fixture로 테스트에 사용하는 객체 작성하기


· 단위 테스트에서 테스트를 실행하기 전·후로  공통 명령어 집합을 실행해야 하는 경우 특정 컴포넌트를 사용해야한다.

ex) 애플리케이션의 구성 상태를 나타내는 객체가 필요한 경우

ex) 테스트에 임시 파일이 필요한 경우 

· fixture라는 컴포넌트는 테스트 전에 설치되고, 테스트가 완료된 후 정리된다. 

· pytest를 사용하면 fixture가 간단한 함수로 정의된다.

 

· 예시1

import pytest


@pytest.fixture
def database():
    return <some database connection>


def test_insert(database):
    database.insert(123)

- 데이터베이스 fixture는 인수 목록에 데이터베이스가 있는 모든 테스트에서 자동으로 사용된다.

- 이러한 방식으로 fixture를 사용하면, 데이터베이스 초기화 코드를 여러 번 반복할 필요가 없다.

 

· 예시2 - fixture를 제너레이터로 구현하여 해제 기능 추가하기

코드 테스트는 fixture를 사용한 후 해제되는 특징이 있다.

import pytest


@pytest.fixture
def database():
    db = <some database connection>
    yield db
    db.close()

def test_insert(database):
    database.insert(123)

- yield 키워드를 사용하고 데이터베이스를 제너레이터로 만들었기 떄문에 테스트가 완료되면 yield 문 이후의 코드가 실행된다. 

- 해당 코드는 테스트가 끝날 때 데이터베이스 연결을 닫는다.

 

· 예시3 - 범위 인수 사용하여 성능 최적화하기

각 테스트에 대한 데이터베이스 연결을 닫으면 테스트가 동일한 연결을 다시 할 수 있기 떄문에 불필요한 비용이 발생할 수 있다.

import pytest


@pytest.fixture(scope="module")
def database():
    db = <some database connection>
    yield db
    db.close()

def test_insert(database):
    database.insert(123)

- 범위 인수를 fixture 데커레이터에 전달하여 fixture의 범위를 지정할 수 있다.

- scope="module" 매개변수를 지정하여 전체 모듈에 대해 한 번 fixture를 초기화하면 데이터베이스 연결을 요청하는 모든 테스트 함수에 동일한 데이터베이스 연결이 전달된다.

 

· 예시 4 - autouse 키워드 사용하기

- fixture를 각 테스트 함수에 대한 인수로 지정하는 대신 autouse 키워드와 함께 사용되는 fixture로 표시하여 테스트 전후에 공통 코드를 실행할 수 있다.

import os
import pytest


@pytest.fixture(autouse=True)
def change_user_env():
    curuser = os.environ.get("USER")
    os.environ["USER"] = "foobar"
    yield
    os.environ["USER"] = curuser

def test_user():
    assert os.getenv("USER") == "foobar"

- 자동 사용 기능은 편리하지만 fixture를 남용하면 해당 범위에 포함된 모든 테스트 전에 fixture가 실행되므로 테스트 실행 속도가 느려질 수 있다. 주의하자.

 

테스트 시나리오 실행하기


· 단위 테스트를 할 때 오류가 발생하는 여러 객체를 처리하는 테스트를 실행하거나, 다른 드라이버에 대해 전체 테스트 도구 모음을 실행할 수 있다.

· 매개변수화된 fixture로 정의된 각 매개변수에 대해 여러 번 사용하는 모든 테스트를 실행하여 이를 쉽게 할 수 있다.

 

·예시

- fixture를 사용하여 mysql, postgresql 다른 매개변수로 테스트를 두 번 실행하는 예제다.

import pytest
import myapp

@pytest.fixture(params=["mysql", "postgresql"])
def database(request):
    db_driver = myapp.driver(request.param)
    db_driver.start()
    yield db_driver
    db_driver.stop()


def test_insert(database):
    database.insert("somedata")

- 드리이버 fixture는 애플리케이션에서 지원하는 데이터베이스 드라이버의 이름을 각각 두 개의 서로 다른 값으로 매개변수화하고, test_insert는 두 번 실행된다.

반응형

댓글