>  기사  >  백엔드 개발  >  Pytest 및 PostgreSQL: 모든 테스트를 위한 새로운 데이터베이스(2부)

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

WBOY
WBOY원래의
2024-09-03 16:09:19717검색

Pytest and PostgreSQL: Fresh database for every test (part II)

이전 게시물에서는 테스트 방법 전/후에 Postgres 데이터베이스를 생성/드롭하는 Pytest Fixture를 만들었습니다. 이 부분에서는 Pytest 팩토리 픽스처를 사용하여 픽스처를 더욱 유연하고 구성 가능하도록 개선하고 싶습니다.

정적 고정물의 한계

예를 들어 테스트에서 모의할 데이터베이스가 두 개 이상인 경우

def test_create_user(test_db1, test_db2):
    ...

거의 두 개의 동일한 조명기를 만들어야 합니다.

TEST_DB_URL = "postgresql://localhost"
TEST_DB1_NAME = "test_foo"
TEST_DB2_NAME = "test_bar"

@pytest.fixture
def test_db1():
    with psycopg.connect(TEST_DB_URL, autocommit=True) as conn:
        cur = conn.cursor()

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

        with psycopg.connect(TEST_DB_URL, dbname=TEST_DB1_NAME) as conn:
            yield conn

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

@pytest.fixture
def test_db2():
    with psycopg.connect(TEST_DB_URL, autocommit=True) as conn:
        cur = conn.cursor()

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

        with psycopg.connect(TEST_DB_URL, dbname=TEST_DB2_NAME) as conn:
            yield conn

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

Pytest 고정물 공장

여기에서는 "정적" 고정 장치가 약간 제한됩니다. 약간의 차이만 있으면 거의 동일하게 필요한 경우 코드를 복제해야 합니다. 다행히 Pytest에는 공장을 고정 장치로 개념이 있습니다.

Factory Fixture는 또 다른 Fixture를 반환하는 Fixture입니다. 모든 Factory와 마찬가지로 함수이기 때문에 반환된 Fixture를 사용자 정의하기 위한 인수를 받아들일 수 있습니다. 관례적으로 make_test_db와 같이 make_* 접두사를 붙일 수 있습니다.

특수 설비

픽스처 팩토리 make_test_db에 대한 유일한 인수는 생성/삭제할 테스트 데이터베이스 이름입니다.

그러므로 make_test_db 팩토리 픽스처를 기반으로 두 개의 "특수" 픽스처를 만들어 보겠습니다.

사용 방법은 다음과 같습니다.

@pytest.fixture
def test_db_foo(make_test_db):
    yield from make_test_db("test_foo")

@pytest.fixture
def test_db_bar(make_test_db):
    yield from make_test_db("test_bar")

참고: 수확량

수익률을 보셨나요? 생성기 내에서 데이터 흐름과 제어를 처리하는 방법에 따라 수율과 수율 사이에 주요 차이점이 있습니다.

Python에서는 yield와 Yield from이 모두 생성기 함수 내에서 사용되어 일련의 값을 생성합니다. 그러나

  • Yield는 생성기 함수의 실행을 일시 중지하고 호출자에게 단일 값을 반환하는 데 사용됩니다.
  • yield from은 값 생성을 다른 생성기에 위임하는 데 사용됩니다. 기본적으로 중첩된 생성기를 "평평화"하여 생성된 값을 외부 생성기의 호출자에게 직접 전달합니다.

즉, 우리는 전문 고정 장치가 아닌 고정 장치 공장에서 "양보"하고 싶습니다. 따라서 여기서는 Yield from이 필요합니다.

데이터베이스 생성/삭제를 위한 픽스처 팩토리

원래 픽스처 생성/삭제 데이터베이스에 필요한 변경 사항은 실제로 코드를 내부 함수로 래핑하는 것 외에는 거의 없습니다.

@pytest.fixture
def make_test_db():
    def _(test_db_name: str):
        with psycopg.connect(TEST_DB_URL, autocommit=True) as conn:
            cur = conn.cursor()

            cur.execute(f'DROP DATABASE IF EXISTS "{test_db_name}" WITH (FORCE)') # type: ignore
            cur.execute(f'CREATE DATABASE "{test_db_name}"') # type: ignore

            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)') # type: ignore

    yield _

보너스: 마이그레이션 픽스처를 공장 픽스처로 다시 작성

이전 부분에서는 방금 생성된 빈 데이터베이스에 Yoyo 마이그레이션을 적용하는 픽스쳐도 있었습니다. 또한 그다지 유연하지도 않았습니다. 동일한 작업을 수행하고 실제 코드를 내부 함수로 래핑해 보겠습니다.

이 경우 코드는 테스트 메서드에서 반환된 후 정리 작업을 수행할 필요가 없기 때문에(수익률 없음)

  • 팩토리 픽스쳐는 (수율이 아닌) 내부 함수를 반환합니다
  • 특수 설비 호출(양보하지 않음) 공장 설비
@pytest.fixture
def make_yoyo():
    """Applies Yoyo migrations to test DB."""
    def _(test_db_name: str, migrations_dir: str):
        url = (
            urlparse(TEST_DB_URL)
            .
            _replace(scheme="postgresql+psycopg")
            .
            _replace(path=test_db_name)
            .geturl()
        )

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

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

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

    return _

@pytest.fixture
def yoyo_foo(make_yoyo):
    migrations_dir = str(Path(__file__, "../../foo/migrations").resolve())
    make_yoyo("test_foo", migrations_dir)

@pytest.fixture
def yoyo_bar(make_yoyo):
    migrations_dir = str(Path(__file__, "../../bar/migrations").resolve())
    make_yoyo("test_bar", migrations_dir)

두 개의 데이터베이스가 필요하고 여기에 마이그레이션을 적용하는 테스트 방법:

from psycopg import Connection

def test_get_new_users_since_last_run(
        test_db_foo: Connection,
        test_db_bar: Connection,
        yoyo_foo,
        yoyo_bar):
    test_db_foo.execute("...")
    ...

결론

Pytest 방법에 대한 데이터베이스를 생성하고 삭제하는 자체 픽스처 팩토리를 구축하는 것은 실제로 Python 생성기와 연산자의 산출량/수익률을 연습하는 데 좋은 연습입니다.

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

위 내용은 Pytest 및 PostgreSQL: 모든 테스트를 위한 새로운 데이터베이스(2부)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.