在 Pytest(每个人最喜欢的 Python 测试框架)中,fixture 是一段可重用的代码,它在测试进入之前安排 某些内容,并在测试退出后进行清理。例如,临时文件或文件夹、设置环境、启动 Web 服务器等。在这篇文章中,我们将了解如何创建一个 Pytest 夹具,该夹具创建一个测试数据库(空或具有已知状态),该数据库获取清理,允许每个测试在完全干净的数据库上运行。
我们将使用 Psycopg 3 创建一个 Pytest 夹具来准备和清理测试数据库。因为空数据库对测试几乎没有帮助,所以我们将选择应用 Yoyo 迁移(在撰写本文时网站已关闭,请转到 archive.org 快照)来填充它。
因此,对本博文中创建的名为 test_db 的 Pytest 夹具的要求是:
通过列出测试方法参数来请求它的任何测试方法:
def test_create_admin_table(test_db): ...
将收到连接到测试数据库的常规 Psycopg 连接实例。测试可以做任何它需要的事情,就像普通的 Psycopg 常见用法一样,例如:
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)
我尝试过 pytest-postgresql,它也有同样的承诺。在编写自己的装置之前我已经尝试过它,但我无法让它为我工作。也许是因为他们的文档让我很困惑。 另一个,pytest-dbt-postgres,我根本没有尝试过。动机和替代方案
看起来有一些 Pytest 插件承诺为依赖数据库的测试提供 PostgreSQL 固定装置。它们可能很适合你。
在经典的Python项目中,源代码位于src/中,测试位于tests/中:
├── src │ └── tuvok │ ├── __init__.py │ └── sales │ └── new_user.py ├── tests │ ├── conftest.py │ └── sales │ └── test_new_user.py ├── requirements.txt └── yoyo.ini
如果你使用像梦幻般的Yoyo这样的迁移库,迁移脚本可能位于migrations/:
├── migrations ├── 20240816_01_Yn3Ca-sales-user-user-add-last-run-table.py ├── ...
我们的测试数据库装置需要很少的配置:
Pytest 有一个天然的地方 conftest.py 用于跨多个文件共享固定装置。灯具配置也会在那里:
# Without DB name! TEST_DB_URL = "postgresql://localhost" TEST_DB_NAME = "test_tuvok" TEST_DB_MIGRATIONS_DIR = str(Path(__file__, "../../migrations").resolve())
您可以从环境变量或任何适合您情况的值中设置这些值。
了解PostgreSQL和Psycopg库,在conftest.py中编写fixture:
@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)')
在我们的例子中,我们使用Yoyo 迁移。将应用迁移编写为另一个名为 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))
如果你想将迁移应用到每个测试数据库,需要 yoyo 夹具用于 test_db 夹具:
@pytest.fixture def test_db(yoyo): ...
要仅将迁移应用于某些测试,需要单独使用yoyo:
def test_create_admin_table(test_db, yoyo): ...
构建自己的装置来为您的测试提供一个干净的数据库对我来说是一次有益的经历,让我能够更深入地研究 Pytest 和 Postgres。
我希望本文对您自己的数据库测试套件有所帮助。请随时在评论中留下您的问题,祝您编码愉快!
以上是Pytest 和 PostgreSQL:每次测试的新数据库的详细内容。更多信息请关注PHP中文网其他相关文章!