"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Pytest y PostgreSQL: base de datos nueva para cada prueba

Pytest y PostgreSQL: base de datos nueva para cada prueba

Publicado el 2024-08-21
Navegar:907

Pytest and PostgreSQL: Fresh database for every test

En Pytest, el marco de pruebas de Python favorito de todos, un dispositivo es un fragmento de código reutilizable que organiza algo antes de que entre la prueba y lo limpia después de que finaliza. Por ejemplo, un archivo o carpeta temporal, entorno de configuración, inicio de un servidor web, etc. En esta publicación, veremos cómo crear un dispositivo Pytest que crea una base de datos de prueba (vacía o con estado conocido) que obtiene limpiado, permitiendo que cada prueba se ejecute en una base de datos completamente limpia.

los objetivos

Crearemos un dispositivo Pytest usando Psycopg 3 para preparar y limpiar la base de datos de prueba. Debido a que una base de datos vacía rara vez es útil para las pruebas, opcionalmente aplicaremos migraciones de Yoyo (en el momento de escribir este artículo, el sitio web está inactivo, vaya a la instantánea de archive.org) para llenarla.

Entonces, los requisitos para el dispositivo Pytest llamado test_db creado en esta publicación de blog son:

  • eliminar la base de datos de prueba si existe antes de la prueba
  • crear una base de datos vacía antes de la prueba
  • opcionalmente aplicar migraciones o crear datos de prueba antes de la prueba
  • proporcionar una conexión a la base de datos de prueba a la prueba
  • caída de la base de datos de prueba después de la prueba (incluso en caso de falla)

Cualquier método de prueba que lo solicite enumerándolo como argumento del método de prueba:

def test_create_admin_table(test_db):
    ...

Recibirá una instancia regular de Psycopg Connection conectada a la base de datos de prueba. La prueba puede hacer lo que sea necesario como con el uso común de Psycopg, por ejemplo:

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)

Motivación y alternativas
Parece que hay algunos complementos de Pytest que prometen accesorios de PostgreSQL para pruebas que dependen de bases de datos. Quizás te funcionen bien.

Probé pytest-postgresql que promete lo mismo. Lo intenté antes de escribir mi propio dispositivo, pero no pude hacerlo funcionar para mí. Tal vez porque sus documentos me resultaron muy confusos.

Otro, pytest-dbt-postgres, no lo he probado en absoluto.


Diseño del archivo del proyecto

En el proyecto clásico de Python, las fuentes se encuentran en src/ y las pruebas en tests/:

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

Si usas la biblioteca de migraciones como el fantástico Yoyo, es probable que los scripts de migración estén en migraciones/:

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

Configuración

Nuestro dispositivo DB de prueba necesitará muy poca configuración:

  • URL de conexión - (sin base de datos)
  • nombre de la base de datos de prueba - se recreará para cada prueba
  • (opcionalmente) carpeta de migraciones - scripts de migraciones para aplicar en cada prueba

Pytest tiene un lugar natural, conftest.py, para compartir dispositivos en varios archivos. La configuración del aparato irá allí también:

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

Puedes establecer estos valores desde la variable de entorno o lo que mejor se adapte a tu caso.

Crear dispositivo test_db

Con conocimiento de PostgreSQL y la biblioteca Psycopg, escriba el dispositivo en 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)')

Crear accesorio de migraciones

En nuestro caso, utilizamos migraciones Yoyo. Escribe aplicar migraciones como otro elemento fijo llamado 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))

Si desea aplicar migraciones a cada base de datos de prueba, solicite el dispositivo yoyo para el dispositivo test_db:

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

Para aplicar la migración solo a algunas pruebas, requiere yoyo individualmente:

def test_create_admin_table(test_db, yoyo):
    ...

Conclusión

Crear mi propio dispositivo para brindarle a sus pruebas una base de datos limpia fue una experiencia gratificante para mí que me permitió profundizar tanto en Pytest como en Postgres.

Espero que este artículo te haya ayudado con tu propio conjunto de pruebas de bases de datos. ¡No dudes en dejarme tu pregunta en los comentarios y feliz codificación!

Declaración de liberación Este artículo se reproduce en: https://dev.to/liborjelinek/pytest-and-postgresql-fresh-database-for-every-test-4eni?1 Si hay alguna infracción, comuníquese con [email protected] para eliminar él
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3