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.
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:
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)
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.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ê.
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 ├── ...
Nosso dispositivo de banco de dados de teste precisará de uma configuração muito pequena:
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.
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)')
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): ...
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!
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