在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中文網其他相關文章!