[Python TDD] pytest

2026. 6. 24. 19:00·BI&Programming-Tools/Python

pytest란?

unittest보다 간결하고 강력한 서드파티 테스트 프레임워크입니다. 실무에서 가장 많이 쓰입니다.

unittest는 Java의 테스트 방식을 따라 클래스와 상속 구조가 필요합니다. pytest는 파이썬답게 함수만으로도 테스트를 작성할 수 있고, 검증도 파이썬 기본 assert문 하나로 처리합니다. 코드가 훨씬 간결해지고 실패 메시지도 더 읽기 쉽습니다.

pytest는 unittest로 작성된 테스트도 그대로 실행할 수 있어서, 기존 unittest 코드를 굳이 바꾸지 않아도 됩니다.

pip install pytest

unittest와의 핵심 차이

  unitest pytest
클래스 상속 unittest.TestCase 필수 불필요, 함수만으로 가능
검증 방법 self.assertEqual() 등 파이썬 기본 assert 문
실행 명령 python -m unittest pytest
실패 메시지 기본 수준 어떤 값이 들어왔는지 상세히 보여줌
공통 데이터 준비 setUp / tearDown @pytest.fixture (더 유연)
여러 케이스 반복 테스트 별도 구현 필요 @pytest.mark.parametrize 지원

 


기본 구조

클래스 없이 함수만으로 테스트를 작성합니다.

unittest에서 필요했던 상속, self, if __name__ 블록이 모두 사라집니다.

# test_filter.py
from filter import filter_by_quality

def test_high_quality_read_passes():
    read = {"seq": "ATCG", "quality": 35}
    assert filter_by_quality(read, threshold=30) == True

def test_low_quality_read_fails():
    read = {"seq": "ATCG", "quality": 15}
    assert filter_by_quality(read, threshold=30) == False

def test_exact_threshold_passes():
    read = {"seq": "ATCG", "quality": 30}
    assert filter_by_quality(read, threshold=30) == True

 

실행 방법

pytest                        # 현재 디렉토리에서 test_*.py 자동 탐색
pytest test_filter.py         # 특정 파일만 실행
pytest test_filter.py -v      # 테스트 이름과 결과를 상세하게 출력
pytest -k "quality"           # 이름에 "quality"가 포함된 테스트만 실행

assert 문

unittest에서 assertEqual, assertIn, assertTrue 등을 구분해서 써야 했던 것과 달리, pytest에서는 파이썬 기본 assert문 하나로 모든 검증을 처리합니다.

assert result == 5
assert result != 0
assert result is None
assert "ATG" in sequence
assert len(reads) == 3
assert result > 0

 

단순해 보이지만 pytest가 assert문을 분석해서 실패 시 어떤 값이 들어왔는지 자세히 보여줍니다.

# 실패했을 때 출력 예시
FAILED test_filter.py::test_high_quality_read_passes
AssertionError: assert False == True
 +  where False = filter_by_quality({'seq': 'ATCG', 'quality': 35}, threshold=30)

 

unittest의 기본 오류 메시지보다 훨씬 읽기 쉽습니다.

예외 발생 확인

import pytest

def test_non_dict_input_raises_type_error():
    with pytest.raises(TypeError):
        filter_by_quality("잘못된입력", threshold=30)

def test_missing_quality_key_raises_value_error():
    read = {"seq": "ATCG"}  # quality 키 없음
    with pytest.raises(ValueError):
        filter_by_quality(read, threshold=30)

fixture — 공통 데이터 준비

unittest의 setUp과 비슷한 역할이지만 더 유연합니다.

setUp은 클래스 안의 모든 테스트 메서드에 일괄 적용됩니다.

pytest의 @pytest.fixture는 함수 인자로 넘겨받는 방식이라, 필요한 테스트에만 선택적으로 적용할 수 있습니다.

import pytest
from filter import filter_by_quality

@pytest.fixture
def sample_reads():
    """여러 테스트에서 공통으로 쓰는 샘플 데이터입니다."""
    return {
        "high": {"seq": "ATCG", "quality": 35},
        "low":  {"seq": "ATCG", "quality": 15},
    }

# fixture 이름을 인자로 넣으면 pytest가 자동으로 주입합니다
def test_high_quality_passes(sample_reads):
    assert filter_by_quality(sample_reads["high"], threshold=30) == True

def test_low_quality_fails(sample_reads):
    assert filter_by_quality(sample_reads["low"], threshold=30) == False

# 이 테스트는 sample_reads가 필요 없어서 인자를 안 받습니다
def test_invalid_input_raises_error():
    with pytest.raises(TypeError):
        filter_by_quality("잘못된입력", threshold=30)

 

fixture도 테스트마다 새로 호출되므로 테스트끼리 데이터를 공유하지 않습니다. (FIRST 원칙의 Independent)

 

fixture scope

기본적으로 테스트 함수 하나마다 fixture가 새로 실행됩니다.

데이터 준비 비용이 크거나(파일 읽기, DB 연결 등) 공유해도 무방한 경우 범위를 넓힐 수 있습니다.

@pytest.fixture(scope="function")  # 기본값, 테스트마다 새로 생성
@pytest.fixture(scope="class")     # 클래스 내 테스트 전체에서 한 번만 생성
@pytest.fixture(scope="module")    # 모듈(파일) 내 테스트 전체에서 한 번만 생성
@pytest.fixture(scope="session")   # 전체 테스트 세션에서 한 번만 생성

 


parametrize — 여러 케이스를 한번에

같은 함수를 여러 입력값으로 반복 테스트할 때 씁니다.

TDD에서 경계값(boundary) 테스트는 중요합니다.

임계값보다 높을 때, 낮을 때, 같을 때, 0일 때 등을 모두 확인해야 하는데, 케이스마다 함수를 따로 만들면 코드가 금방 길어집니다. parametrize를 쓰면 데이터만 추가하는 방식으로 깔끔하게 관리할 수 있습니다.

import pytest
from filter import filter_by_quality

@pytest.mark.parametrize("quality, threshold, expected", [
    (35, 30, True),   # 임계값보다 높음 → 통과
    (15, 30, False),  # 임계값보다 낮음 → 실패
    (30, 30, True),   # 임계값과 동일  → 통과 (경계값)
    (0,  30, False),  # 최솟값         → 실패
])
def test_filter_quality(quality, threshold, expected):
    read = {"seq": "ATCG", "quality": quality}
    assert filter_by_quality(read, threshold=threshold) == expected
pytest test_filter.py -v
# test_filter.py::test_filter_quality[35-30-True] PASSED
# test_filter.py::test_filter_quality[15-30-False] PASSED
# test_filter.py::test_filter_quality[30-30-True] PASSED
# test_filter.py::test_filter_quality[0-30-False] PASSED

 

새로운 케이스가 생기면 리스트에 튜플 하나만 추가하면 됩니다.


 

conftest.py

여러 테스트 파일에서 공통으로 쓰는 fixture를 모아두는 파일입니다.

pytest가 자동으로 인식하기 때문에 별도 import 없이 사용할 수 있습니다.

project/
├── src/
│   └── filter.py
└── tests/
    ├── conftest.py       # 공통 fixture 모음
    ├── test_filter.py
    └── test_parser.py
# conftest.py
import pytest

@pytest.fixture
def sample_reads():
    return {
        "high": {"seq": "ATCG", "quality": 35},
        "low":  {"seq": "ATCG", "quality": 15},
    }

 

test_filter.py와 test_parser.py 양쪽에서 sample_reads를 import 없이 그대로 인자로 받아 쓸 수 있습니다.

공통 데이터를 한 곳에서 관리할 수 있어서 수정이 생겼을 때 편합니다.


 

유용한 실행 옵션

pytest -v                  # 각 테스트 이름과 결과를 상세 출력
pytest -s                  # print() 출력을 터미널에 보여줌 (기본은 숨김)
pytest --tb=short          # 실패 시 traceback을 간략히 출력
pytest --tb=long           # 실패 시 traceback을 상세히 출력
pytest -x                  # 첫 번째 실패에서 바로 멈춤
pytest --lf                # 마지막 실행에서 실패한 테스트만 재실행
pytest -k "quality"        # 이름에 "quality"가 포함된 테스트만 실행
pytest -k "not error"      # 이름에 "error"가 없는 테스트만 실행

 

개발 중에는 -x와 --lf를 조합해서 쓰는 경우가 많습니다.

실패한 것만 빠르게 재실행하고, 통과하면 다음으로 넘어가는 방식입니다.

pytest --lf -x   # 마지막 실패 테스트만, 첫 실패에서 멈춤

'BI&Programming-Tools > Python' 카테고리의 다른 글

[파이썬] 함수 데코레이터  (0) 2026.06.27
[Python TDD] Mocking  (0) 2026.06.24
[Python TDD] unittest  (0) 2026.06.24
TDD 개념 — Test Driven Development  (0) 2026.06.24
Python 커맨드 라인 인자 받기 — sys.argv & argparse  (0) 2026.06.23
'BI&Programming-Tools/Python' 카테고리의 다른 글
  • [파이썬] 함수 데코레이터
  • [Python TDD] Mocking
  • [Python TDD] unittest
  • TDD 개념 — Test Driven Development
데이터로 읽는 생명
데이터로 읽는 생명
is-note 님의 블로그 입니다.
  • 데이터로 읽는 생명
    In Silico Note
    데이터로 읽는 생명
  • 전체
    오늘
    어제
    • 분류 전체보기 (190)
      • Bio-Knowledge (16)
        • 분자생물학 & 유전학 기초 (0)
        • 전사체학 & 유전자 발현 (0)
        • 구조생물학 & 단백질 (0)
        • 싱글셀 & 다중오믹스 (0)
        • 임상유전학 & 질환 데이터 (0)
      • Programming (4)
        • API (4)
      • BI&Programming-Tools (111)
        • File Formats (14)
        • Linux & Bash Script (38)
        • Python (35)
        • R (11)
        • 통계 (8)
        • Pipeline Manager (0)
        • Etc (5)
      • Bio Data Analysis (28)
        • 서열분석개론 (6)
        • WGS(Whole Genome Seq) (11)
        • WES(Whole Exome Seq) (2)
        • RNA-Seq (4)
        • Metagenome (2)
        • Non-human Resequencing (1)
        • 임상유전체 분석 (1)
        • Multi-Omics (1)
      • Bio-Trends & Tech (1)
      • 코딩테스트 연습 (30)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    bioinformatics
    파이썬연습
    파일포맷
    데이터분석
    코딩테스트
    fasta
    분자생물학
    GATK
    R
    생물정보학
    유전체분석
    리눅스기초
    FASTQ
    통계
    R기초
    wgs
    파이썬
    리눅스
    파이썬기초
    ngs
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
데이터로 읽는 생명
[Python TDD] pytest
상단으로

티스토리툴바