
들어가며
금요일 오후, 간단한 스키마 변경 후 배포했는데 월요일 아침에 데이터 불일치 버그가 쏟아진 경험이 있을 것이다. 이런 문제는 대부분 테스트의 신뢰도 부족에서 시작된다. 특히 데이터베이스 구조 변경은 코드 전체에 예상치 못한 영향을 준다. 이 글에서는 pytest와 SQLite를 기준으로, 데이터베이스 구조를 자동으로 검증하는 세 가지 핵심 원리를 정리한다. 이 원리들을 적용하면 스키마 변경이 훨씬 덜 두려워진다.
참고: 본 글의 예제는 SQLite 기준이다. PostgreSQL이나 MySQL에서는 스키마 조회 방식이 다르다.
1. 테스트는 항상 같은 출발선에서 시작한다: @pytest.fixture
견고한 테스트의 첫 번째 원칙은 격리다. 각 테스트는 이전 테스트의 결과에 영향을 받아서는 안 된다. pytest에서는 @pytest.fixture를 사용해 이 문제를 해결한다. fixture는 테스트 실행 전의 준비(setup)와 실행 후의 정리(teardown)를 담당한다. 이때 핵심은 yield다.
fixture 동작 흐름
- fixture가 실행된다.
- 기존 DB 파일이 있으면 삭제한다.
- init_db()를 호출해 새로운 데이터베이스를 만든다.
- yield에서 테스트 함수로 제어가 넘어간다.
- 테스트가 실행된다.
- 테스트 성공/실패와 관계없이 다시 fixture로 돌아온다.
- DB 파일을 삭제하고 정리한다.
예제: 데이터베이스 fixture
import os
import sqlite3
import pytest
DB_PATH = "test.db"
def init_db():
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE auth_tokens (
token TEXT NOT NULL,
user_id INTEGER NOT NULL
)
""")
conn.commit()
conn.close()
@pytest.fixture
def setup_database():
if os.path.exists(DB_PATH):
os.remove(DB_PATH)
init_db()
yield
if os.path.exists(DB_PATH):
os.remove(DB_PATH)
이 방식의 핵심은 단순하다.
테스트마다 항상 새 데이터베이스를 만든다
이렇게 하면 테스트 순서나 실행 횟수에 따라 결과가 달라지는 문제를 원천적으로 막을 수 있다.
2. 데이터가 아니라 설계도를 검증한다: PRAGMA table_info
많은 테스트는 데이터 삽입과 조회만 확인한다. 하지만 스키마가 잘못되면 데이터 테스트 자체가 의미 없어질 수 있다. SQLite에서는 PRAGMA table_info(테이블명)을 사용해 테이블 구조를 조회할 수 있다. 이 명령은 각 컬럼의 정보를 행(row) 단위로 반환한다. 중요한 값은 다음 두 가지다.
| 인덱스 | 의미 |
| row[1] | 컬럼 이름 |
| row[2] | 컬럼 타입 |
이 결과를 딕셔너리로 변환하면 검증이 쉬워진다.
columns = {row[1]: row[2] for row in cursor.fetchall()}
실제 스키마 검증 테스트
def test_auth_tokens_table_schema(setup_database):
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(auth_tokens)")
columns = {row[1]: row[2] for row in cursor.fetchall()}
assert "token" in columns
assert columns["token"] == "TEXT"
assert "user_id" in columns
assert columns["user_id"] == "INTEGER"
conn.close()
이 테스트는 다음을 자동으로 검증한다.
- auth_tokens 테이블이 존재하는가?
- token 컬럼이 있는가?
- 타입이 TEXT인가?
❌ 실패 사례 예시
만약 실수로 스키마를 이렇게 바꿨다면,
token INTEGER
테스트 결과는 다음과 같이 실패한다.
AssertionError: assert 'INTEGER' == 'TEXT'
이 실패는 배포 전에 바로 감지된다. 이것이 스키마 테스트의 가장 큰 가치다.
3. 필요한 것만 선언한다: pytest의 의존성 주입
pytest에서는 fixture를 직접 호출하지 않는다. 테스트 함수의 파라미터로 선언하기만 하면 된다.
def test_auth_tokens_table_schema(setup_database):
...
pytest는 이 함수를 실행하기 전에 다음을 자동으로 처리한다.
- setup_database fixture 실행
- 데이터베이스 준비
- 테스트 실행
- 정리 작업 수행
이 방식이 바로 의존성 주입이다. 개발자는 “이 테스트에는 이 환경이 필요하다”라고 선언만 하면 된다. 준비 과정은 프레임워크가 책임진다.
마무리
이 글에서 소개한 세 가지 원리는 하나의 흐름으로 연결된다.
- @pytest.fixture가 테스트 환경을 완전히 격리하고
- PRAGMA table_info가 데이터베이스 설계도를 직접 검증하며
- pytest의 의존성 주입이 이 과정을 간결하게 만든다.
이 테스트가 궁극적으로 답해주는 질문은 하나다.
init_db 함수가 정말 의도한 대로 테이블과 컬럼을 만들고 있는가?
이 질문에 자동으로 답해주는 테스트가 있다면, 스키마 변경은 더 이상 공포가 아니다. 이제 자신의 프로젝트를 점검해 보자. 데이터베이스 구조를 자동으로 검증하는 테스트가 준비되어 있는가?
'잡(job)기술 > 파이썬 공부' 카테고리의 다른 글
| 이제야 알게 된 Python else의 힘 (1) | 2026.01.12 |
|---|---|
| 내 코드가 외부 API와 통신한 척하게 만드는 방법 - 파이썬 Mocking 핵심 개념 2가지 (3) | 2026.01.02 |
| 파이썬 sqlite3, 혹시 이렇게 쓰고 있는가? - 나의 코드를 바꿔놓을 4가지 핵심 팁 (0) | 2025.12.31 |
| Poetry로 Python 프로젝트 환경 만들기 (2) | 2025.06.24 |
| pyproject.toml 시작하기: Python 프로젝트의 현대적 설정 파일 (2) | 2025.06.24 |