Heim >Backend-Entwicklung >Python-Tutorial >Pytest und PostgreSQL: Frische Datenbank für jeden Test

Pytest und PostgreSQL: Frische Datenbank für jeden Test

WBOY
WBOYOriginal
2024-08-19 16:43:42487Durchsuche

Pytest and PostgreSQL: Fresh database for every test

In Pytest, dem beliebtesten Python-Testframework aller, ist ein Fixture ein wiederverwendbarer Codeabschnitt, der etwas anordnet, bevor der Test beginnt, und nach dem Beenden aufräumt. Zum Beispiel eine temporäre Datei oder einen temporären Ordner, eine Setup-Umgebung, das Starten eines Webservers usw. In diesem Beitrag sehen wir uns an, wie man ein Pytest-Fixture erstellt, das eine Testdatenbank (leer oder mit bekanntem Status) erstellt, die abgerufen wird bereinigt, sodass jeder Test auf einer völlig sauberen Datenbank ausgeführt werden kann.

Die Ziele

Wir werden mit Psycopg 3 ein Pytest-Gerät erstellen, um die Testdatenbank vorzubereiten und zu bereinigen. Da eine leere Datenbank zum Testen selten hilfreich ist, werden wir optional Yoyo-Migrationen anwenden (zum Zeitpunkt des Schreibens ist die Website nicht verfügbar, gehen Sie zum Snapshot von archive.org), um sie aufzufüllen.

Die Anforderungen für das Pytest-Fixture namens test_db, das in diesem Blogbeitrag erstellt wurde, sind also:

  • Testdatenbank löschen falls vor dem Test vorhanden
  • Erstellen Sie vor dem Test eine leere Datenbank
  • optional
  • Migrationen anwenden oder Testdaten erstellenvor dem Test
  • Stellen Sie eine Verbindung zur Testdatenbank herzum Test
  • Testdatenbank fallenlassennach dem Test (auch im Fehlerfall)
Jede Testmethode, die dies anfordert, indem sie ein Testmethodenargument auflistet:


def test_create_admin_table(test_db):
    ...
Erhält eine reguläre Psycopg Connection-Instanz, die mit der Test-DB verbunden ist. Der Test kann alles tun, was er benötigt, wie bei der allgemeinen Verwendung von Psycopg, z. B.:


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)

Motivation & Alternativen Es sieht so aus, als gäbe es einige Pytest-Plugins, die PostgreSQL-Fixtures für Tests versprechen, die auf Datenbanken basieren. Sie könnten für Sie gut funktionieren.
Ich habe pytest-postgresql ausprobiert, was dasselbe verspricht. Ich habe es versucht, bevor ich mein eigenes Fixture geschrieben habe, aber ich konnte es nicht für mich zum Laufen bringen. Vielleicht, weil ihre Dokumente für mich sehr verwirrend waren.

Noch eins, pytest-dbt-postgres, ich habe es überhaupt nicht versucht.



Layout der Projektdatei

In klassischen Python-Projekten befinden sich die Quellen in src/ und Tests in tests/:


├── src
│   └── tuvok
│       ├── __init__.py
│       └── sales
│           └── new_user.py
├── tests
│   ├── conftest.py
│   └── sales
│       └── test_new_user.py
├── requirements.txt
└── yoyo.ini
Wenn Sie eine Migrationsbibliothek wie das fantastische Yoyo verwenden, befinden sich Migrationsskripte wahrscheinlich in migrations/:


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

Unser Test-DB-Gerät benötigt nur eine sehr kleine Konfiguration:

  • Verbindungs-URL - (ohne Datenbank)
  • Name der Testdatenbank – wird für jeden Test neu erstellt
  • (optional)
  • Migrationsordner – Migrationsskripte, die für jeden Test angewendet werden sollen
Pytest verfügt über einen natürlichen Ort conftest.py zum Teilen von Fixtures über mehrere Dateien hinweg. Dort wird auch die Gerätekonfiguration abgelegt:


# Without DB name!
TEST_DB_URL = "postgresql://localhost"
TEST_DB_NAME = "test_tuvok"
TEST_DB_MIGRATIONS_DIR = str(Path(__file__, "../../migrations").resolve())
Sie können diese Werte über die Umgebungsvariable oder was auch immer für Ihren Fall geeignet ist festlegen.

Erstellen Sie test_db-Fixture

Mit Kenntnissen der

PostgreSQL- und Psycopg-Bibliothek schreiben Sie das Fixture in 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)')
Erstellen Sie eine Migrationseinrichtung

In unserem Fall verwenden wir

Yoyo-Migrationen. Schreiben Sie apply migrations als ein weiteres Fixture mit dem Namen 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))
Wenn Sie

Migrationen auf jede Testdatenbank anwenden möchten, benötigen Sie ein Yoyo-Fixture für test_db-Fixture:

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

die Migration nur auf einige Tests anzuwenden, erfordern Sie Yoyo einzeln:

def test_create_admin_table(test_db, yoyo):
    ...
Abschluss

Das Erstellen einer eigenen Vorrichtung, um Ihren Tests eine saubere Datenbank zu bieten, war für mich eine lohnende Erfahrung, die es mir ermöglichte, tiefer in Pytest und Postgres einzutauchen.

Ich hoffe, dieser Artikel hat Ihnen bei Ihrer eigenen Datenbank-Testsuite geholfen. Hinterlassen Sie mir Ihre Frage gerne in den Kommentaren und viel Spaß beim Codieren!

Das obige ist der detaillierte Inhalt vonPytest und PostgreSQL: Frische Datenbank für jeden Test. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn