Kodokon kodokon.com

pytestによるテスト:フィクスチャ、パラメーター化、ベストプラクティス

pytestで堅牢なテストを組み立てましょう。フィクスチャの注入、yieldによる後片付け、表形式のパラメーター化、そして広いスコープの落とし穴です。

9 分 · 3 問

このレッスンを Kodokon で開く

pytestは二つの強い発想の上に成り立っています。規約による発見(test_*.pyというファイル、test_*という関数)と、素のassertです。assertのイントロスペクションは、失敗したときに両辺の実際の値を表示してくれます - もうassertEqualとその三十もの派生形を覚える必要はありません。すぐに知っておくべき道具が二つあります。例外が確かに送出されることを確かめるpytest.raisesと、二進の丸めと戦わずに浮動小数点数を比較するpytest.approxです。

PYTHON
import pytest

def divide(a: float, b: float) -> float:
    return a / b

def test_nominal() -> None:
    assert divide(8, 4) == 2.0

def test_zero_division() -> None:
    with pytest.raises(ZeroDivisionError):
        divide(1, 0)

def test_floats() -> None:
    assert 0.1 + 0.2 == pytest.approx(0.3)
approxなしでは、二進の浮動小数点数で0.1 + 0.2 == 0.3は偽になる

フィクスチャはpytestの依存性注入です。フィクスチャの名前を持つパラメーターを宣言すれば、pytestがそれを組み立てて注入してくれます。yieldは準備と後片付けを分けます - そのあとのコードはテストが失敗しても実行されます。scopeパラメーター(デフォルトはfunction、ほかにmodulesession)はオブジェクトの寿命を制御します。共有するフィクスチャはconftest.pyに置きましょう。インポートを一つも書かずに、フォルダー全体で使えるようになります。

PYTHON
import sqlite3

import pytest

@pytest.fixture
def db():
    conn = sqlite3.connect(":memory:")
    conn.execute("CREATE TABLE users (name TEXT)")
    yield conn
    conn.close()

def test_insert(db) -> None:
    db.execute("INSERT INTO users VALUES ('Ada')")
    query = "SELECT name FROM users"
    rows = db.execute(query).fetchall()
    assert rows == [("Ada",)]
conn.close()はテストが成功しても失敗しても、テストのあとに実行される

@pytest.mark.parametrizeはテストをケースの表に変えます。各タプルが独立したテストになり、個別に名前が付いて絞り込めます。parametrizeデコレーターを重ねれば、パラメーターの直積が得られます。pytest.param(..., id='...')でケースに読みやすいidを付けましょう - 失敗のレポートは、ソースファイルを開かなくても理解できるべきです。

PYTHON
import pytest

def slugify(text: str) -> str:
    return "-".join(text.lower().split())

@pytest.mark.parametrize(
    ("raw", "expected"),
    [
        ("Hello World", "hello-world"),
        ("  Python  rocks ", "python-rocks"),
        pytest.param("", "", id="empty"),
    ],
)
def test_slugify(raw: str, expected: str) -> None:
    assert slugify(raw) == expected
三つのケース、レポート上では三つの別々のテスト
BASH
pytest -x --lf
pytest -k "slugify and not empty"
pytest -q --durations=5
--lfは失敗したものだけを再実行する。--durationsは遅いテストを暴く

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. pytestはどのフィクスチャをテストに渡せばよいと、どうやって知るのですか?
    • テストに付けられた@injectデコレーターのおかげで
    • ファイルの中でフィクスチャが宣言された順序で
    • パラメーターの型アノテーションで
    • パラメーターの名前で。見えているフィクスチャ(モジュールまたはconftest.py)と一致すれば、pytestがそれを組み立てて注入する
  2. フィクスチャの中で、yieldのあとに置かれたコードは何のためのものですか?
    • それは後片付けで、テストが失敗してもテストのあとに実行される
    • 決して実行されない。yieldがフィクスチャを終わらせる
    • 準備が失敗したときの代替の値を提供する
    • テストが失敗したときだけ実行される
  3. pytest --lfというコマンドは何をしますか?
    • プロジェクトで使えるフィクスチャを一覧表示する
    • 前回の実行で失敗したテストだけを再実行する
    • 最初に出くわした失敗でセッションを止める