Pytest的Postgresql fixture和fixture工厂。
项目描述
pytest-postgresql
这是什么?
这是一个pytest插件,它允许您测试依赖于运行中的PostgreSQL数据库的代码。它允许您为PostgreSQL进程和客户端指定fixture。
如何使用
使用以下方式安装
pip install pytest-postgresql您还需要安装 psycopg。请参阅其安装说明。请注意,此插件需要psycopg版本3。对于需要后者(请参阅这些说明)的库,可以同时安装版本3和版本2。
插件包含三个fixture
- postgresql - 它是一个具有功能范围的客户端配置。每次测试后,它都会结束所有剩余的连接,并从PostgreSQL中删除测试数据库,确保可重复性。此配置返回已经连接的psycopg连接。 
- postgresql_proc - 会话范围配置,首次使用时启动PostgreSQL实例,在测试结束时停止。 
- postgresql_noproc - 一个无进程配置,连接到已经运行的PostgreSQL实例。例如,在Docker化的测试环境中,或CI提供PostgreSQL服务。 
只需将其中之一包含到您的测试配置文件列表中。
如果您需要,也可以创建额外的PostgreSQL客户端和进程配置。
from pytest_postgresql import factories
postgresql_my_proc = factories.postgresql_proc(
    port=None, unixsocketdir='/var/run')
postgresql_my = factories.postgresql('postgresql_my_proc')示例测试
def test_example_postgres(postgresql):
    """Check main postgresql fixture."""
    cur = postgresql.cursor()
    cur.execute("CREATE TABLE test (id serial PRIMARY KEY, num integer, data varchar);")
    postgresql.commit()
    cur.close()如果您想自动将模式填充到数据库配置中,有两种方法
- 客户端配置特定 
- 进程配置特定 
两者都接受相同的可能加载器集合
- SQL文件路径 
- 加载函数导入路径(字符串) 
- 实际的加载函数 
该函数将接收 host、port、user、dbname 和 password 关键字参数,并需要在内部执行数据库连接。但是,您将能够运行SQL文件甚至触发编程数据库迁移。
客户端特定每次测试都会加载数据库
from pathlib import Path
postgresql_my_with_schema = factories.postgresql(
    'postgresql_my_proc',
    load=[Path("schemafile.sql"), Path("otherschema.sql"), "import.path.to.function", "import.path.to:otherfunction", load_this]
)进程配置在每个测试会话中只加载一次,并将数据加载到模板数据库中。然后,客户端配置在每个测试中从模板数据库中创建测试数据库,这可以显著 加快测试速度。
from pathlib import Path
postgresql_my_proc = factories.postgresql_proc(
    load=[Path("schemafile.sql"), Path("otherschema.sql"), "import.path.to.function", "import.path.to:otherfunction", load_this]
)pytest --postgresql-populate-template=path.to.loading_function --postgresql-populate-template=path.to.other:loading_function --postgresql-populate-template=path/to/file.sql示例中的loading_function将接收,并需要提交。
连接到现有的PostgreSQL数据库
一些项目正在使用已经运行的PostgreSQL服务器(例如在Docker实例上)。为了连接到它们,人们将使用 postgresql_noproc 配置。
postgresql_external = factories.postgresql('postgresql_noproc')默认情况下,postgresql_noproc 配置将连接到使用 5432 端口的PostgreSQL实例。标准配置选项适用于它。
这些是适用于所有级别与 postgresql_noproc 配置的配置选项
配置
您可以通过三种方式定义设置,它是配置工厂参数、命令行选项和pytest.ini配置选项。您可以选择您喜欢的,但请记住,这些设置按照以下顺序处理
配置工厂参数
命令行选项
pytest.ini文件中的配置选项
| PostgreSQL选项 | 配置工厂参数 | 命令行选项 | pytest.ini选项 | 无操作进程配置 | 默认 | 
|---|---|---|---|---|---|
| 可执行文件路径 | 可执行文件 | –postgresql-exec | postgresql_exec | /usr/lib/postgresql/13/bin/pg_ctl | |
| host | host | –postgresql-host | postgresql_host | yes | 127.0.0.1 | 
| port | port | –postgresql-port | postgresql_port | yes (5432) | random | 
| postgresql用户 | user | –postgresql-user | postgresql_user | yes | postgres | 
| password | password | –postgresql-password | postgresql_password | yes | |
| 启动参数(额外的pg_ctl参数) | startparams | –postgresql-startparams | postgresql_startparams | -w | |
| Postgres可执行文件额外参数(通过pg_ctl的-o参数传递) | postgres_options | –postgresql-postgres-options | postgresql_postgres_options | ||
| Unixsocket位置 | unixsocket | –postgresql-unixsocketdir | postgresql_unixsocketdir | $TMPDIR | |
| 由配置文件创建的数据库名称 | dbname | –postgresql-dbname | postgresql_dbname | 是的,但是使用xdist时,会向名称中添加索引,因此每个工作进程的结果是test0、test1。 | 测试 | 
| 默认模式,可以是sql文件或导入路径到将加载它的函数(每个的值列表) | 加载 | –postgresql-load | postgresql_load | yes | |
| PostgreSQL连接选项 | 选项 | –postgresql-options | postgresql_options | yes | 
示例用法
- 将其作为参数传递到您自己的 fixture 中 - postgresql_proc = factories.postgresql_proc( port=8888)
- 在运行测试时,使用 --postgresql-port 命令行选项 - py.test tests --postgresql-port=8888
- 在您的 pytest.ini 文件中将端口指定为 postgresql_port。 - 为此,在 pytest.ini 的 [pytest] 部分下添加如下一行 - [pytest] postgresql_port = 8888
示例
为测试填充数据库
使用 SQLAlchemy
此示例演示了如何填充数据库并创建 SQLAlchemy 的 ORM 连接
以下示例是从 pyramid_fullauth 测试中简化后的 session fixture
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.pool import NullPool
from zope.sqlalchemy import register
@pytest.fixture
def db_session(postgresql):
    """Session for SQLAlchemy."""
    from pyramid_fullauth.models import Base
    connection = f'postgresql+psycopg2://{postgresql.info.user}:@{postgresql.info.host}:{postgresql.info.port}/{postgresql.info.dbname}'
    engine = create_engine(connection, echo=False, poolclass=NullPool)
    pyramid_basemodel.Session = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
    pyramid_basemodel.bind_engine(
        engine, pyramid_basemodel.Session, should_create=True, should_drop=True)
    yield pyramid_basemodel.Session
    transaction.commit()
    Base.metadata.drop_all(engine)
@pytest.fixture
def user(db_session):
    """Test user fixture."""
    from pyramid_fullauth.models import User
    from tests.tools import DEFAULT_USER
    new_user = User(**DEFAULT_USER)
    db_session.add(new_user)
    transaction.commit()
    return new_user
def test_remove_last_admin(db_session, user):
    """
    Sample test checks internal login, but shows usage in tests with SQLAlchemy
    """
    user = db_session.merge(user)
    user.is_admin = True
    transaction.commit()
    user = db_session.merge(user)
    with pytest.raises(AttributeError):
        user.is_admin = False在 fixtures 之外维护数据库状态
使用 pytest-postgresql 数据库管理功能,维护数据库状态是可能的,并且似乎在测试的库中使用
为此导入 DatabaseJanitor 并使用其 init 和 drop 方法
import pytest
from pytest_postgresql.janitor import DatabaseJanitor
@pytest.fixture
def database(postgresql_proc):
    # variable definition
    janitor = DatabaseJanitor(
        postgresql_proc.user,
        postgresql_proc.host,
        postgresql_proc.port,
        "my_test_database",
        postgresql_proc.version,
        password="secret_password",
    )
    janitor.init()
    yield psycopg2.connect(
        dbname="my_test_database",
        user=postgresql_proc.user,
        password="secret_password",
        host=postgresql_proc.host,
        port=postgresql_proc.port,
    )
    janitor.drop()或将其用作上下文管理器
import pytest
from pytest_postgresql.janitor import DatabaseJanitor
@pytest.fixture
def database(postgresql_proc):
    # variable definition
    with DatabaseJanitor(
        postgresql_proc.user,
        postgresql_proc.host,
        postgresql_proc.port,
        "my_test_database",
        postgresql_proc.version,
        password="secret_password",
    ):
        yield psycopg2.connect(
            dbname="my_test_database",
            user=postgresql_proc.user,
            password="secret_password",
            host=postgresql_proc.host,
            port=postgresql_proc.port,
        )连接到 Postgresql(在 docker 中)
要连接到运行在 docker 中的 postgresql 并在上面运行测试,请使用 noproc fixtures。
docker run --name some-postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres这将启动在 docker 容器中的 postgresql,但是使用本地安装的 postgresql 并没有太大区别。
在测试中,请确保所有测试都使用 postgresql_noproc fixture,如下所示
from pytest_postgresql import factories
postgresql_in_docker = factories.postgresql_noproc()
postgresql = factories.postgresql("postgresql_in_docker", dbname="test")
def test_postgres_docker(postgresql):
    """Run test."""
    cur = postgresql.cursor()
    cur.execute("CREATE TABLE test (id serial PRIMARY KEY, num integer, data varchar);")
    postgresql.commit()
    cur.close()然后运行测试
pytest --postgresql-host=172.17.0.2 --postgresql-password=mysecretpassword所有测试的基本数据库状态
如果您有多个需要共同初始化的测试,您可以定义一个 load 并将其传递到您的自定义 postgresql 进程 fixture
import pytest_postgresql.factories
def load_database(**kwargs):
    db_connection: connection = psycopg2.connect(**kwargs)
    with db_connection.cursor() as cur:
        cur.execute("CREATE TABLE stories (id serial PRIMARY KEY, name varchar);")
        cur.execute(
            "INSERT INTO stories (name) VALUES"
            "('Silmarillion'), ('Star Wars'), ('The Expanse'), ('Battlestar Galactica')"
        )
        db_connection.commit()
postgresql_proc = factories.postgresql_proc(
    load=[load_database],
)
postgresql = factories.postgresql(
    "postgresql_proc",
)此过程将是进程 fixture 填充模板数据库,然后客户端 fixture 将自动使用它来从头开始创建测试数据库。快速、干净且没有悬空事务,这些事务可能被意外回滚。
相同的做法将适用于 noproces fixture,无论连接到的是在 docker 机器上运行还是远程或本地运行的已运行的 postgresql 实例。
使用 SQLAlchemy 初始化基本数据库状态
如何使用 SQLAlchemy 进行公共初始化
def load_database(**kwargs):
    connection = f"postgresql+psycopg2://{kwargs['user']}:@{kwargs['host']}:{kwargs['port']}/{kwargs['dbname']}"
    engine = create_engine(connection)
    Base.metadata.create_all(engine)
    session = scoped_session(sessionmaker(bind=engine))
    # add things to session
    session.commit()
postgresql_proc = factories.postgresql_proc(load=[load_database])
postgresql = factories.postgresql('postgresql_proc') # still need to check if this is actually needed or not
@pytest.fixture
def dbsession(postgresql):
    connection = f'postgresql+psycopg2://{postgresql.info.user}:@{postgresql.info.host}:{postgresql.info.port}/{postgresql.info.dbname}'
    engine = create_engine(connection)
    session = scoped_session(sessionmaker(bind=engine))
    yield session
    # 'Base.metadata.drop_all(engine)' here specifically does not work. It is also not needed. If you leave out the session.close()
    # all the tests still run, but you get a warning/error at the end of the tests.
    session.close()发布
首先安装 pipenv 和 –dev 依赖项,然后运行
pipenv run tbump [NEW_VERSION]项目详细信息
pytest_postgresql-6.1.1.tar.gz的哈希值
| 算法 | 哈希摘要 | |
|---|---|---|
| SHA256 | f996637367e6aecebba1349da52eea95340bdb434c90e4b79739e62c656056e2 | |
| MD5 | f04e0b064516775517861b1a63d97be0 | |
| BLAKE2b-256 | 8851d56a56fd852f0e3da5d001cb90815c793f904e387495e63e22ea90d2de1d | 
pytest_postgresql-6.1.1-py3-none-any.whl的哈希值
| 算法 | 哈希摘要 | |
|---|---|---|
| SHA256 | bd4c0970d25685ac3d34d42263fcbfbf134bf02d22519fce7e1ccf4122d8b99a | |
| MD5 | 677439a2dbfd48a956a9c4f415e2b95e | |
| BLAKE2b-256 | dbc8ca523b7dac9253cbfd60466aa0993b4fe5c558844585e7a0c0ad21d00bec |