"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > Pytest 및 PostgreSQL: 모든 테스트를 위한 새로운 데이터베이스

Pytest 및 PostgreSQL: 모든 테스트를 위한 새로운 데이터베이스

2024-08-21에 게시됨
검색:495

Pytest and PostgreSQL: Fresh database for every test

모두가 선호하는 Python 테스트 프레임워크인 Pytest에서 픽스쳐는 테스트가 시작되기 전에 무언가를 정렬하고 종료 후에 정리하는 재사용 가능한 코드 조각입니다. 예를 들어 임시 파일이나 폴더, 환경 설정, 웹 서버 시작 등이 있습니다. 이 게시물에서는 다음을 가져오는 테스트 데이터베이스(비어 있거나 알려진 상태)를 생성하는 Pytest 픽스처를 생성하는 방법을 살펴보겠습니다. 정리되어 각 테스트가 완전히 깨끗한 데이터베이스에서 실행될 수 있습니다.

목표

Psycopg 3을 사용하여 Pytest 픽스쳐를 만들어 테스트 데이터베이스를 준비하고 정리하겠습니다. 빈 데이터베이스는 테스트에 거의 도움이 되지 않기 때문에 선택적으로 Yoyo 마이그레이션(웹 사이트 작성 시 archive.org 스냅샷으로 이동)을 적용하여 채울 것입니다.

따라서 이 블로그 게시물에서 생성된 test_db라는 Pytest 픽스처에 대한 요구 사항은 다음과 같습니다.

  • 테스트 데이터베이스 삭제 테스트 전에 존재하는 경우
  • 테스트 전에 빈 데이터베이스 만들기
  • 선택적으로 테스트 전에 마이그레이션을 적용하거나 테스트 데이터를 생성
  • 테스트 데이터베이스에 대한 연결 제공 테스트에
  • 테스트 데이터베이스 삭제 테스트 후(실패한 경우에도)

테스트 메서드 인수를 나열하여 요청하는 모든 테스트 메서드:

def test_create_admin_table(test_db):
    ...

테스트 DB에 연결된 일반 Psycopg 연결 인스턴스를 수신합니다. 테스트는 일반 Psycopg 일반적인 사용법과 같이 필요한 모든 작업을 수행할 수 있습니다. 예:

def test_create_admin_table(test_db):
    # Open a cursor to perform database operations
    cur = test_db.cursor()

    # Pass data to fill a query placeholders and let Psycopg perform
    # the correct conversion (no SQL injections!)
    cur.execute(
        "INSERT INTO test (num, data) VALUES (%s, %s)",
        (100, "abc'def"))

    # Query the database and obtain data as Python objects.
    cur.execute("SELECT * FROM test")
    cur.fetchone()
    # will return (1, 100, "abc'def")

    # You can use `cur.fetchmany()`, `cur.fetchall()` to return a list
    # of several records, or even iterate on the cursor
    for record in cur:
        print(record)

동기 부여 및 대안
데이터베이스에 의존하는 테스트를 위한 PostgreSQL 고정 장치를 약속하는 일부 Pytest 플러그인이 있는 것 같습니다. 그들은 당신에게 잘 작동할 수도 있습니다.

저는 동일한 결과를 약속하는 pytest-postgresql을 사용해 보았습니다. 나는 내 자신의 픽스처를 작성하기 전에 그것을 시도했지만 나에게 적합하게 만들 수 없었습니다. 어쩌면 그들의 문서가 나에게 매우 혼란스럽기 때문일 수도 있습니다.

또 다른 pytest-dbt-postgres는 전혀 시도하지 않았습니다.


프로젝트 파일 레이아웃

클래식 Python 프로젝트에서 소스는 src/에 있고 테스트는 test/:
에 있습니다.

├── src
│   └── tuvok
│       ├── __init__.py
│       └── sales
│           └── new_user.py
├── tests
│   ├── conftest.py
│   └── sales
│       └── test_new_user.py
├── requirements.txt
└── yoyo.ini

환상적인 Yoyo와 같은 마이그레이션 라이브러리를 사용하는 경우 마이그레이션 스크립트가 migrations/:
에 있을 가능성이 높습니다.

├── migrations
    ├── 20240816_01_Yn3Ca-sales-user-user-add-last-run-table.py
    ├── ...

구성

테스트 DB 고정 장치에는 약간의 구성이 필요합니다.

  • 연결 URL - (데이터베이스 없음)
  • 테스트 데이터베이스 이름 - 모든 테스트에 대해 다시 생성됩니다.
  • (선택 사항) 마이그레이션 폴더 - 모든 테스트에 적용할 마이그레이션 스크립트

Pytest에는 여러 파일에 걸쳐 픽스쳐를 공유하기 위한 자연스러운 장소인 conftest.py가 있습니다. 조명기 구성도 거기에 갈 것입니다:

# Without DB name!
TEST_DB_URL = "postgresql://localhost"
TEST_DB_NAME = "test_tuvok"
TEST_DB_MIGRATIONS_DIR = str(Path(__file__, "../../migrations").resolve())

이러한 값은 환경 변수 또는 귀하의 사례에 맞는 다른 변수에서 설정할 수 있습니다.

test_db 고정 장치 생성

PostgreSQL 및 Psycopg 라이브러리에 대한 지식을 바탕으로 conftest.py에 픽스쳐를 작성합니다:

@pytest.fixture
def test_db():
    # autocommit=True start no transaction because CREATE/DROP DATABASE
    # cannot be executed in a transaction block.
    with psycopg.connect(TEST_DB_URL, autocommit=True) as conn:
        cur = conn.cursor()

        # create test DB, drop before
        cur.execute(f'DROP DATABASE IF EXISTS "{TEST_DB_NAME}" WITH (FORCE)')
        cur.execute(f'CREATE DATABASE "{TEST_DB_NAME}"')

        # Return (a new) connection to just created test DB
        # Unfortunately, you cannot directly change the database for an existing Psycopg connection. Once a connection is established to a specific database, it's tied to that database.
        with psycopg.connect(TEST_DB_URL, dbname=TEST_DB_NAME) as conn:
            yield conn

        cur.execute(f'DROP DATABASE IF EXISTS "{TEST_DB_NAME}" WITH (FORCE)')

마이그레이션 픽스쳐 생성

우리의 경우에는 Yoyo 마이그레이션을 사용합니다. yoyo라는 또 다른 픽스처로 적용 마이그레이션을 작성합니다:

@pytest.fixture
def yoyo():
    # Yoyo expect `driver://user:pass@host:port/database_name?param=value`.
    # In passed URL we need to
    url = (
        urlparse(TEST_DB_URL)
        .
        # 1) Change driver (schema part) with `postgresql psycopg` to use
        # psycopg 3 (not 2 which is `postgresql psycopg2`)
        _replace(scheme="postgresql psycopg")
        .
        # 2) Change database to test db (in which migrations will apply)
        _replace(path=TEST_DB_NAME)
        .geturl()
    )

    backend = get_backend(url)
    migrations = read_migrations(TEST_DB_MIGRATIONS_DIR)

    if len(migrations) == 0:
        raise ValueError(f"No Yoyo migrations found in '{TEST_DB_MIGRATIONS_DIR}'")

    with backend.lock():
        backend.apply_migrations(backend.to_apply(migrations))

모든 테스트 데이터베이스에 마이그레이션을 적용하려면 test_db 고정 장치에 요요 고정 장치가 필요합니다.

@pytest.fixture
def test_db(yoyo):
    ...

일부 테스트에만 마이그레이션을 적용하려면 개별적으로 yoyo를 요구합니다.

def test_create_admin_table(test_db, yoyo):
    ...

결론

테스트에 깨끗한 데이터베이스를 제공하기 위해 자체 픽스처를 구축하는 것은 Pytest와 Postgres에 대해 더 깊이 탐구할 수 있는 보람 있는 경험이었습니다.

이 기사가 귀하의 데이터베이스 테스트 스위트에 도움이 되었기를 바랍니다. 궁금한 점을 댓글로 남겨주시면 즐거운 코딩이 되실 것입니다!

릴리스 선언문 이 기사는 https://dev.to/liborjelinek/pytest-and-postgresql-fresh-database-for-every-test-4eni ?1에서 재현됩니다.
최신 튜토리얼 더>

부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.

Copyright© 2022 湘ICP备2022001581号-3