Testcontainers for Python 시작하기
Testcontainers for Python 시작하기
이 가이드에서는 Testcontainers for Python을 이용해 실제 PostgreSQL 인스턴스로 Python 애플리케이션을 만들고 데이터베이스 상호작용을 테스트하는 방법을 배워요.
출처: 문서
본문
이 가이드를 통해 다음 내용을 배울 수 있어요.
- PostgreSQL을 사용해 고객 데이터를 저장하는 Python 애플리케이션 만들기
- 데이터베이스와 상호작용하기 위해
psycopg사용하기 testcontainers-python과pytest로 통합 테스트 작성하기- pytest 픽스처(fixture)로 컨테이너 수명주기 관리하기
사전 준비 (Prerequisites)
- Python 3.10 이상
- pip
- Testcontainers가 지원하는 Docker 환경
참고: Testcontainers가 처음이라면 Testcontainers 개요를 방문해 알아보는 걸 권장해요.
Python 프로젝트 만들기
가상 환경과 함께 Python 프로젝트를 만들어요.
$ mkdir tc-python-demo
$ cd tc-python-demo
$ python3 -m venv venv
$ source venv/bin/activate
이 가이드는 Postgres 데이터베이스와 상호작용하기 위해 psycopg3, 테스트를 위해 pytest, 컨테이너에서 PostgreSQL 데이터베이스를 실행하기 위해 testcontainers-python을 사용해요.
의존성을 설치해요.
$ pip install "psycopg[binary]" pytest testcontainers[postgres]
$ pip freeze > requirements.txt
pip freeze 명령은 requirements.txt 파일을 생성해서, 다른 사람들이 pip install -r requirements.txt로 같은 패키지 버전을 설치할 수 있게 해요.
데이터베이스 헬퍼 만들기
데이터베이스 연결을 얻는 함수를 가진 db/connection.py 파일을 만들어요.
import os
import psycopg
def get_connection():
host = os.getenv("DB_HOST", "localhost")
port = os.getenv("DB_PORT", "5432")
username = os.getenv("DB_USERNAME", "postgres")
password = os.getenv("DB_PASSWORD", "postgres")
database = os.getenv("DB_NAME", "postgres")
return psycopg.connect(f"host={host} dbname={database} user={username} password={password} port={port}")
데이터베이스 연결 매개변수를 하드코딩하는 대신, 이 함수는 환경 변수를 사용해요. 이렇게 하면 코드를 바꾸지 않고도 여러 환경에서 애플리케이션을 실행할 수 있어요.
비즈니스 로직 만들기
customers/customers.py 파일을 만들고 Customer 클래스를 정의해요.
class Customer:
def __init__(self, cust_id, name, email):
self.id = cust_id
self.name = name
self.email = email
def __str__(self):
return f"Customer({self.id}, {self.name}, {self.email})"
customers 테이블을 만드는 create_table() 함수를 추가해요.
from db.connection import get_connection
def create_table():
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute("""
CREATE TABLE customers (
id serial PRIMARY KEY,
name varchar not null,
email varchar not null unique)
""")
conn.commit()
이 함수는 get_connection()으로 데이터베이스 연결을 얻고 customers 테이블을 만들어요. with 문은 작업이 끝나면 연결을 자동으로 닫아요.
나머지 CRUD 함수를 추가해요.
def create_customer(name, email):
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute("INSERT INTO customers (name, email) VALUES (%s, %s)", (name, email))
conn.commit()
def get_all_customers() -> list[Customer]:
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute("SELECT * FROM customers")
return [Customer(cid, name, email) for cid, name, email in cur]
def get_customer_by_email(email) -> Customer:
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute("SELECT id, name, email FROM customers WHERE email = %s", (email,))
(cid, name, email) = cur.fetchone()
return Customer(cid, name, email)
def delete_all_customers():
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute("DELETE FROM customers")
conn.commit()
참고: 이 가이드를 단순하게 유지하기 위해 각 함수가 새 연결을 만들어요. 실제 애플리케이션에서는 연결 풀(connection pool)을 사용해 연결을 재사용하는 게 좋아요.
Testcontainers로 테스트 작성하기
Testcontainers로 PostgreSQL 컨테이너를 만들고 모든 테스트에 사용할 거예요. 각 테스트 전에 모든 고객 레코드를 삭제해서 테스트가 깨끗한 데이터베이스로 실행되게 해요.
pytest 픽스처 설정하기
이 가이드는 설정과 정리 로직에 pytest 픽스처를 사용해요. 권장되는 방법은 파이널라이저(finalizer)를 사용해서 설정이 실패하더라도 정리가 실행되도록 보장하는 거예요.
@pytest.fixture
def setup(request):
# setup code
def cleanup():
# teardown code
request.addfinalizer(cleanup)
return some_value
테스트 파일 만들기
pytest 자동 발견(auto-discovery)을 활성화하려면 빈 내용의 tests/__init__.py 파일을 만들어요. 그런 다음 픽스처와 함께 tests/test_customers.py를 만들어요.
import os
import pytest
from testcontainers.postgres import PostgresContainer
from customers import customers
postgres = PostgresContainer("postgres:16-alpine")
@pytest.fixture(scope="module", autouse=True)
def setup(request):
postgres.start()
def remove_container():
postgres.stop()
request.addfinalizer(remove_container)
os.environ["DB_CONN"] = postgres.get_connection_url()
os.environ["DB_HOST"] = postgres.get_container_host_ip()
os.environ["DB_PORT"] = str(postgres.get_exposed_port(5432))
os.environ["DB_USERNAME"] = postgres.username
os.environ["DB_PASSWORD"] = postgres.password
os.environ["DB_NAME"] = postgres.dbname
customers.create_table()
@pytest.fixture(scope="function", autouse=True)
def setup_data():
customers.delete_all_customers()
픽스처가 하는 일을 살펴보면,
setup픽스처는scope="module"이므로 파일의 모든 테스트에 대해 한 번 실행돼요. PostgreSQL 컨테이너를 시작하고, 연결 정보로 환경 변수를 설정하며,customers테이블을 만들어요. 정리 함수는 모든 테스트가 끝난 후 컨테이너를 제거해요.setup_data픽스처는scope="function"이므로 매 테스트 전에 실행돼요. 모든 레코드를 삭제해서 각 테스트가 깨끗한 데이터베이스를 갖게 해요.
테스트 작성하기
같은 파일에 테스트 함수를 추가해요.
def test_get_all_customers():
customers.create_customer("Siva", "[email protected]")
customers.create_customer("James", "[email protected]")
customers_list = customers.get_all_customers()
assert len(customers_list) == 2
def test_get_customer_by_email():
customers.create_customer("John", "[email protected]")
customer = customers.get_customer_by_email("[email protected]")
assert customer.name == "John"
assert customer.email == "[email protected]"
test_get_all_customers()는 고객 레코드 두 개를 삽입하고 모든 고객을 가져와 개수를 단언해요. test_get_customer_by_email()은 고객을 삽입하고 이메일로 조회해 상세 정보를 단언해요. setup_data가 각 테스트 전에 모든 레코드를 삭제하므로, 테스트는 어떤 순서로든 실행될 수 있어요.
테스트 실행과 다음 단계
pytest로 테스트를 실행해요.
$ pytest -v
다음과 비슷한 출력이 보여야 해요.
============================= test session starts ==============================
platform linux -- Python 3.13.x, pytest-9.x.x
collected 2 items
tests/test_customers.py::test_get_all_customers PASSED [ 50%]
tests/test_customers.py::test_get_customer_by_email PASSED [100%]
============================== 2 passed in 1.90s ===============================
테스트는 목(mock) 대신 실제 PostgreSQL 데이터베이스에 대해 실행되므로 구현에 더 큰 확신을 줘요.
요약 (Summary)
Testcontainers for Python 라이브러리는 목(mock) 대신 프로덕션에서 쓰는 것과 같은 종류의 데이터베이스(Postgres)를 사용해 통합 테스트를 작성할 수 있게 도와줘요. 목을 쓰지 않고 실제 서비스와 대화하기 때문에, 코드를 리팩터링해도 애플리케이션이 예상대로 동작하는지 검증할 수 있답니다.
PostgreSQL 외에도 Testcontainers for Python은 많은 SQL 데이터베이스, NoSQL 데이터베이스, 메시징 큐 등을 위한 모듈을 제공해요. 테스트에 필요한 어떤 컨테이너화된 의존성이든 Testcontainers로 실행할 수 있어요.
Testcontainers에 대해 더 알아보고 싶다면 Testcontainers 개요를 방문해요.
더 읽어보기 (Further reading)
- testcontainers-python 문서
- Testcontainers for Go 시작하기
- Testcontainers for Java 시작하기
- Testcontainers for Node.js 시작하기