Maison > Article > développement back-end > Pytest et PostgreSQL : nouvelle base de données pour chaque test (partie II)
Dans l'article précédent, nous avons créé le luminaire Pytest qui créera/supprimera la base de données Postgres avant/après la méthode de test. Dans cette partie, je souhaite améliorer le luminaire pour qu'il soit plus flexible et configurable à l'aide des luminaires d'usine Pytest.
Par exemple, si vous avez plus d'une base de données à simuler dans le test
def test_create_user(test_db1, test_db2): ...
vous devez créer presque deux luminaires identiques :
TEST_DB_URL = "postgresql://localhost" TEST_DB1_NAME = "test_foo" TEST_DB2_NAME = "test_bar" @pytest.fixture def test_db1(): with psycopg.connect(TEST_DB_URL, autocommit=True) as conn: cur = conn.cursor() cur.execute(f'DROP DATABASE IF EXISTS "{TEST_DB1_NAME}" WITH (FORCE)') cur.execute(f'CREATE DATABASE "{TEST_DB1_NAME}"') with psycopg.connect(TEST_DB_URL, dbname=TEST_DB1_NAME) as conn: yield conn cur.execute(f'DROP DATABASE IF EXISTS "{TEST_DB1_NAME}" WITH (FORCE)') @pytest.fixture def test_db2(): with psycopg.connect(TEST_DB_URL, autocommit=True) as conn: cur = conn.cursor() cur.execute(f'DROP DATABASE IF EXISTS "{TEST_DB2_NAME}" WITH (FORCE)') cur.execute(f'CREATE DATABASE "{TEST_DB2_NAME}"') with psycopg.connect(TEST_DB_URL, dbname=TEST_DB2_NAME) as conn: yield conn cur.execute(f'DROP DATABASE IF EXISTS "{TEST_DB2_NAME}" WITH (FORCE)')
Les luminaires "statiques" sont un peu limitatifs ici. Lorsque vous en avez besoin à peu près de la même manière avec juste une légère différence, vous devez dupliquer un code. Espérons que le Pytest ait un concept d'usines comme accessoires.
Un luminaire d'usine est un luminaire qui renvoie un autre luminaire. Parce que, comme toute usine, c'est une fonction, elle peut accepter des arguments pour personnaliser les luminaires renvoyés. Par convention, vous pouvez les préfixer avec make_*, comme make_test_db.
Le seul argument de notre fabrique d'appareils make_test_db sera un nom de base de données de test à créer/supprimer.
Alors, créons deux luminaires "spécialisés" basés sur le luminaire d'usine make_test_db.
L'utilisation ressemblera à :
@pytest.fixture def test_db_foo(make_test_db): yield from make_test_db("test_foo") @pytest.fixture def test_db_bar(make_test_db): yield from make_test_db("test_bar")
Avez-vous remarqué le rendement de ? Il existe une différence clé entre le rendement et le rendement selon la manière dont ils gèrent le flux de données et le contrôle au sein des générateurs.
En Python, le rendement et le rendement de sont utilisés dans les fonctions du générateur pour produire une séquence de valeurs, mais
C'est-à-dire nous ne voulons pas "céder" d'un luminaire spécialisé mais d'une usine de luminaires. Par conséquent, le rendement de est requis ici.
Les modifications requises dans notre base de données de création/suppression de luminaires d'origine ne sont en fait presque aucune, à l'exception de l'encapsulation du code dans la fonction interne.
@pytest.fixture def make_test_db(): def _(test_db_name: str): with psycopg.connect(TEST_DB_URL, autocommit=True) as conn: cur = conn.cursor() cur.execute(f'DROP DATABASE IF EXISTS "{test_db_name}" WITH (FORCE)') # type: ignore cur.execute(f'CREATE DATABASE "{test_db_name}"') # type: ignore 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)') # type: ignore yield _
Dans la partie précédente, j'ai également eu un appareil appliquant les migrations Yoyo à une base de données vide venant d'être créée. Ce n’était pas non plus très flexible. Faisons de même et enveloppons le code réel dans la fonction interne.
Dans ce cas, comme le code n'a pas besoin d'effectuer un nettoyage après le retour de la méthode de test (aucun rendement), le
@pytest.fixture def make_yoyo(): """Applies Yoyo migrations to test DB.""" def _(test_db_name: str, migrations_dir: str): url = ( urlparse(TEST_DB_URL) . _replace(scheme="postgresql+psycopg") . _replace(path=test_db_name) .geturl() ) backend = get_backend(url) migrations = read_migrations(migrations_dir) if len(migrations) == 0: raise ValueError(f"No Yoyo migrations found in '{migrations_dir}'") with backend.lock(): backend.apply_migrations(backend.to_apply(migrations)) return _ @pytest.fixture def yoyo_foo(make_yoyo): migrations_dir = str(Path(__file__, "../../foo/migrations").resolve()) make_yoyo("test_foo", migrations_dir) @pytest.fixture def yoyo_bar(make_yoyo): migrations_dir = str(Path(__file__, "../../bar/migrations").resolve()) make_yoyo("test_bar", migrations_dir)
Une méthode de test qui nécessite deux bases de données et leur applique des migrations :
from psycopg import Connection def test_get_new_users_since_last_run( test_db_foo: Connection, test_db_bar: Connection, yoyo_foo, yoyo_bar): test_db_foo.execute("...") ...
Construire votre propre fabrique de luminaires en créant et en supprimant des bases de données pour la méthode Pytest est en fait un bon exercice pour pratiquer le générateur Python et le rendement/rendement des opérateurs.
J'espère que cet article vous a aidé avec votre propre suite de tests de base de données. N'hésitez pas à me laisser votre question dans les commentaires et bon codage !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!