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.
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:
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)
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.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.
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 ├── ...
Nuestro dispositivo DB de prueba necesitará muy poca configuración:
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.
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)')
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): ...
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!
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