"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Pytest e PostgreSQL: banco de dados novo para cada teste

Pytest e PostgreSQL: banco de dados novo para cada teste

Publicado em 2024-08-21
Navegar:643

Pytest and PostgreSQL: Fresh database for every test

No Pytest, a estrutura de teste Python favorita de todos, um fixture é um pedaço de código reutilizável que organiza algo antes do teste entrar e faz a limpeza depois que ele termina. Por exemplo, um arquivo ou pasta temporária, ambiente de configuração, inicialização de um servidor web, etc. Neste post, veremos como criar um fixture Pytest que cria um banco de dados de teste (vazio ou com estado conhecido) que obtém limpo, permitindo que cada teste seja executado em um banco de dados completamente limpo.

Os objetivos

Criaremos um acessório Pytest usando Psycopg 3 para preparar e limpar o banco de dados de teste. Como um banco de dados vazio raramente é útil para testes, opcionalmente aplicaremos migrações Yoyo (no momento em que este artigo foi escrito, o site está fora do ar, acesse o snapshot archive.org) para preenchê-lo.

Portanto, os requisitos para o acessório Pytest chamado test_db criado nesta postagem do blog são:

  • descartar banco de dados de teste se existir antes do teste
  • crie um banco de dados vazio antes do teste
  • opcionalmente aplicar migrações ou criar dados de teste antes do teste
  • fornecer uma conexão ao banco de dados de teste ao teste
  • descartar banco de dados de teste após o teste (mesmo em caso de falha)

Qualquer método de teste que o solicite listando-o como um argumento de método de teste:

def test_create_admin_table(test_db):
    ...

Receberá uma instância regular do Psycopg Connection conectada ao banco de dados de teste. O teste pode fazer o que for necessário, como acontece com o uso comum do Psycopg, por exemplo:

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)

Motivação e alternativas
Parece que existem alguns plug-ins Pytest que prometem acessórios PostgreSQL para testes que dependem de bancos de dados. Eles podem funcionar bem para você.

Eu tentei o pytest-postgresql que promete o mesmo. Eu tentei antes de escrever meu próprio fixture, mas não consegui fazê-lo funcionar para mim. Talvez porque os documentos deles fossem muito confusos para mim.

Outro, pytest-dbt-postgres, ainda não tentei.


Layout do arquivo do projeto

No projeto Python clássico, as fontes residem em src/ e os testes em testes/:

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

Se você usar uma biblioteca de migrações como o fantástico Yoyo, os scripts de migração provavelmente estarão em migrações/:

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

Configuração

Nosso dispositivo de banco de dados de teste precisará de uma configuração muito pequena:

  • URL de conexão - (sem banco de dados)
  • nome do banco de dados de teste - será recriado para cada teste
  • (opcionalmente) pasta de migrações - scripts de migração para aplicar em todos os testes

Pytest tem um local natural conftest.py para compartilhar equipamentos em vários arquivos. A configuração do fixture também irá para lá:

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

Você pode definir esses valores a partir da variável de ambiente ou o que for mais adequado ao seu caso.

Criar acessório test_db

Com conhecimento do PostgreSQL e da biblioteca Psycopg, escreva o fixture em 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)')

Criar dispositivo de migração

No nosso caso, usamos Migrações Yoyo. Escreva apply migrações como outro fixture chamado 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))

Se você deseja aplicar migrações a todos os bancos de dados de teste, exija o acessório yoyo para o acessório test_db:

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

Para aplicar a migração apenas para alguns testes, exija o yoyo individualmente:

def test_create_admin_table(test_db, yoyo):
    ...

Conclusão

Construir seu próprio equipamento para fornecer aos seus testes um banco de dados limpo foi uma experiência gratificante para mim, permitindo-me me aprofundar no Pytest e no Postgres.

Espero que este artigo tenha ajudado você com seu próprio conjunto de testes de banco de dados. Sinta-se à vontade para me deixar sua pergunta nos comentários e boa codificação!

Declaração de lançamento Este artigo é reproduzido em: https://dev.to/liborjelinek/pytest-and-postgresql-fresh-database-for-very-test-4eni?1 Se houver alguma infração, entre em contato com [email protected] para excluí-lo.
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3