Python pytestの使い方完全ガイド|fixture・parametrize・conftest.pyを実務レベルで解説

テスト自動化

pytestはPythonで最も広く使われているテストフレームワークで、シンプルな記法と強力なフィクスチャ機能により、QAエンジニアのテスト自動化を劇的に効率化します。

📌 この記事はこんな方におすすめ

  • Pythonでテスト自動化を始めたいQAエンジニア・開発者
  • pytestの基本から fixture・parametrize まで体系的に学びたい方
  • unittest から pytest へ移行を検討している方
  • PlaywrightやSeleniumと組み合わせてテストを管理したい方

✅ この記事を読むと得られること

  • pytestのインストールから最初のテスト実行まで迷わず進められる
  • fixture・parametrize・conftest.py を使いこなせるようになる
  • 実務レベルのテスト構成・レポート出力まで一気通貫で理解できる

👤 この記事を書いた人

QAエンジニアとして実務でSelenium・Playwright・pytestを活用したテスト自動化に従事。実際のプロジェクトで書いてきたコードをベースに、現場で本当に使える知識だけを厳選して解説しています。実装コードはGitHubで公開中:github.com/YOSHITSUGU728/automated-testing-portfolio

📌 結論:pytestで押さえるべき3つのポイント

  • シンプルな構文assert文だけでテストが書ける、学習コストが低いフレームワーク
  • fixture でテストを賢く管理:前処理・後処理・データ共有を宣言的に記述できる
  • parametrize で網羅性を高める:1つの関数で複数パターンのテストを効率よく実行

テスト自動化の学習ロードマップの中で、pytestはPythonと並んで最初に習得すべきフレームワークです。Playwright・Seleniumなどの自動化ツールと組み合わせて使うことがほとんどで、テストの実行・管理・レポート出力まで一手に引き受けてくれます。この記事では、インストールから実務で使えるレベルまで、ステップごとに丁寧に解説します。

pytestとは?unittest との違い

pytestはPython向けのオープンソーステストフレームワークです。標準ライブラリのunittestと比べて、記述量が少なく、エラーメッセージが読みやすく、プラグインエコシステムが豊富という特徴があります。

比較項目unittestpytest
記述スタイルクラス継承が必要関数ベース(クラス不要)
アサーションassertEqual, assertTrue などassert文1つで完結
エラーメッセージシンプル詳細で読みやすい
フィクスチャsetUp / tearDown@pytest.fixture(柔軟)
プラグイン少ないpytest-html, allure など豊富
外部ツールとの相性普通Playwright・Selenium と抜群

① インストール・環境構築

まずはpytestをインストールします。Python 3.8以上が必要です。

# pytestのインストール
pip install pytest

# バージョン確認
pytest --version

# よく使うプラグインもまとめてインストール
pip install pytest pytest-html pytest-xdist

プロジェクト構成の例:

my_project/
├── src/
│   └── calculator.py       # テスト対象のコード
├── tests/
│   ├── conftest.py         # 共通フィクスチャ置き場
│   ├── test_calculator.py  # テストファイル
│   └── test_api.py
└── pytest.ini              # pytest設定ファイル
💡 ポイント:テストファイルは必ず test_ で始める(または _test.py で終わる)命名規則があります。pytest はこのルールに従ってテストを自動検出します。

② 基本的なテストの書き方

まずはテスト対象のシンプルな関数を用意します。

# src/calculator.py

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def divide(a, b):
    if b == 0:
        raise ValueError("0で割ることはできません")
    return a / b

次に、対応するテストを書きます:

# tests/test_calculator.py
import pytest
from src.calculator import add, subtract, divide

# ✅ 基本的なテスト関数(関数名は test_ で始める)
def test_add_正常系():
    result = add(3, 5)
    assert result == 8

def test_subtract_正常系():
    result = subtract(10, 4)
    assert result == 6

def test_divide_正常系():
    result = divide(10, 2)
    assert result == 5.0

# ✅ 例外が発生することをテストする
def test_divide_ゼロ除算():
    with pytest.raises(ValueError) as exc_info:
        divide(10, 0)
    assert "0で割ることはできません" in str(exc_info.value)

テストの実行

# すべてのテストを実行
pytest

# 特定ファイルだけ実行
pytest tests/test_calculator.py

# 詳細表示(-v)
pytest -v

# テスト名を絞り込み(-k)
pytest -k "divide"

# 最初の失敗で停止(-x)
pytest -x

実行結果サンプル

========================= test session starts ==========================
platform win32 -- Python 3.11.0, pytest-7.4.0
collected 4 items

tests/test_calculator.py::test_add_正常系           PASSED  [ 25%]
tests/test_calculator.py::test_subtract_正常系      PASSED  [ 50%]
tests/test_calculator.py::test_divide_正常系        PASSED  [ 75%]
tests/test_calculator.py::test_divide_ゼロ除算      PASSED  [100%]

========================== 4 passed in 0.12s ===========================

③ fixture(フィクスチャ)の使い方

fixtureはテストの前処理・後処理・共有データを管理するpytestの中核機能です。毎回同じセットアップコードを書く必要がなくなり、テストがスッキリします。

# tests/test_user.py
import pytest

# --- フィクスチャの定義 ---
@pytest.fixture
def sample_user():
    """テスト用ユーザーデータを返すフィクスチャ"""
    return {
        "id": 1,
        "name": "テスト太郎",
        "email": "test@example.com",
        "role": "admin"
    }

@pytest.fixture
def empty_list():
    return []

# --- フィクスチャを使ったテスト ---
def test_user_name(sample_user):
    assert sample_user["name"] == "テスト太郎"

def test_user_role(sample_user):
    assert sample_user["role"] == "admin"

def test_empty_list_is_empty(empty_list):
    assert len(empty_list) == 0

setup / teardown(前処理・後処理)

yieldを使うと、テスト前後の処理を1つのfixtureにまとめられます:

@pytest.fixture
def db_connection():
    # --- 前処理(テスト開始前) ---
    print("\n[SETUP] DBに接続中...")
    connection = {"status": "connected", "db": "test_db"}

    yield connection  # ← ここでテストに渡す

    # --- 後処理(テスト終了後) ---
    print("\n[TEARDOWN] DB接続を閉じました")
    connection["status"] = "disconnected"

def test_db_is_connected(db_connection):
    assert db_connection["status"] == "connected"

fixtureのスコープ(scope)

scope実行タイミング用途
function(デフォルト)テスト関数ごと軽量なデータ準備
classクラスごとクラス単位の共有
moduleファイルごとファイル内で使い回し
sessionテスト実行全体で1回DB接続・ブラウザ起動など重い処理
# セッション全体で1回だけブラウザを起動する例(Playwright連携)
@pytest.fixture(scope="session")
def browser():
    from playwright.sync_api import sync_playwright
    with sync_playwright() as p:
        browser = p.chromium.launch()
        yield browser
        browser.close()

④ parametrize(パラメータ化テスト)

@pytest.mark.parametrizeを使うと、同じテストロジックを複数のパターンで実行できます。境界値テストや正常系・異常系の網羅に非常に有効です。

import pytest
from src.calculator import add, divide

# ✅ 基本的なパラメータ化
@pytest.mark.parametrize("a, b, expected", [
    (1, 2, 3),      # 正の整数
    (-1, -2, -3),   # 負の整数
    (0, 5, 5),      # ゼロを含む
    (100, 200, 300) # 大きな数
])
def test_add_パラメータ化(a, b, expected):
    assert add(a, b) == expected

# ✅ 複数の異常系をまとめてテスト
@pytest.mark.parametrize("a, b", [
    (10, 0),
    (0, 0),
    (-5, 0),
])
def test_divide_ゼロ除算_パラメータ化(a, b):
    with pytest.raises(ValueError):
        divide(a, b)

実行すると、パラメータごとに独立したテストとして表示されます:

tests/test_calculator.py::test_add_パラメータ化[1-2-3]        PASSED
tests/test_calculator.py::test_add_パラメータ化[-1--2--3]     PASSED
tests/test_calculator.py::test_add_パラメータ化[0-5-5]        PASSED
tests/test_calculator.py::test_add_パラメータ化[100-200-300]  PASSED

⑤ conftest.py の活用

conftest.pyはfixtureを複数のテストファイルで共有するための特別なファイルです。ここに書いたfixtureはimport不要で全テストから参照できます。

※ 実際のコードでは base_url の戻り値に http:// を付けてください(例:"http://localhost:8080")。

# tests/conftest.py

import pytest

@pytest.fixture(scope="session")
def base_url():
    """テスト対象のベースURL(全テストで共有)"""
    return "localhost:8080"

@pytest.fixture
def test_user_credentials():
    """テスト用ログイン情報"""
    return {
        "username": "standard_user",
        "password": "secret_sauce"
    }

@pytest.fixture(scope="session")
def api_headers():
    """APIテスト用の共通ヘッダー"""
    return {
        "Content-Type": "application/json",
        "Accept": "application/json"
    }
# tests/test_login.py
# ← conftest.py のフィクスチャはimport不要でそのまま使える!

def test_login_with_valid_credentials(test_user_credentials, base_url):
    username = test_user_credentials["username"]
    password = test_user_credentials["password"]
    # ... テストロジック

⑥ マーク(mark)の活用

pytestのマーク機能を使うと、テストのスキップや条件付き実行が簡単にできます。

import pytest
import sys

# ✅ テストをスキップする
@pytest.mark.skip(reason="未実装のため一時スキップ")
def test_未実装機能():
    assert some_future_function() == True

# ✅ 条件付きスキップ(WindowsのみスキップするなどOS依存テストに有効)
@pytest.mark.skipif(sys.platform == "win32", reason="Windowsでは非対応")
def test_linux_only():
    assert True

# ✅ 既知のバグ(失敗が想定されるテスト)
@pytest.mark.xfail(reason="バグ修正待ち: issue #123")
def test_known_bug():
    assert 1 + 1 == 3  # 意図的に失敗

# ✅ カスタムマーク(グループ化して絞り込み実行できる)
@pytest.mark.smoke
def test_ログイン_スモークテスト():
    assert True

@pytest.mark.regression
def test_決済フロー_回帰テスト():
    assert True
# スモークテストだけ実行
pytest -m smoke

# 回帰テストだけ実行
pytest -m regression

# カスタムマークを使うには pytest.ini に登録が必要
# pytest.ini:
# [pytest]
# markers =
#     smoke: スモークテスト
#     regression: 回帰テスト

⑦ pytest.ini の設定

プロジェクトルートにpytest.iniを置くと、pytest全体の動作をカスタマイズできます:

# pytest.ini
[pytest]
# テストファイルの検索パス
testpaths = tests

# デフォルトオプション(毎回 -v --tb=short を付けているのと同じ)
addopts = -v --tb=short --html=reports/report.html --self-contained-html

# カスタムマークの登録(警告を消すために必要)
markers =
    smoke: スモークテスト(最小限の動作確認)
    regression: 回帰テスト
    slow: 実行時間が長いテスト

# ログ出力設定
log_cli = true
log_cli_level = INFO

⑧ HTMLレポートの出力

pytest-htmlプラグインを使うと、テスト結果をHTMLファイルとして出力できます。CI/CDでの結果共有やQAレポートとして非常に便利です。

# インストール
pip install pytest-html

# HTMLレポートを出力
pytest --html=reports/report.html --self-contained-html

# pytest.ini に追加すれば毎回自動生成
# addopts = -v --html=reports/report.html --self-contained-html

生成されたレポートは reports/report.html をブラウザで開くと確認できます。テスト名・合否・実行時間・エラー詳細が一覧表示されます。

💡 実務Tip--self-contained-htmlオプションを付けると、CSSとJSが1ファイルに埋め込まれるのでメールやSlackで共有しやすくなります。

⑨ Playwright × pytest の組み合わせ例

pytestはPlaywrightと組み合わせることで、E2Eテストをより整理された形で管理できます。pytest-playwrightプラグインを使うのが最も簡単です。

# インストール
pip install pytest-playwright
playwright install chromium
# tests/test_saucedemo.py
import pytest
from playwright.sync_api import Page

# pytest-playwright が提供する page フィクスチャを引数に受け取るだけでOK
# ※ 実際のコードでは page.goto() の引数に http:// を付けてください(例:http://localhost:8080/)
def test_ログイン_正常系(page: Page):
    page.goto("localhost:8080/")
    page.fill("#user-name", "standard_user")
    page.fill("#password", "secret_sauce")
    page.click("#login-button")
    assert page.url.endswith("/inventory.html")

def test_ログイン_パスワード誤り(page: Page):
    page.goto("localhost:8080/")
    page.fill("#user-name", "standard_user")
    page.fill("#password", "wrong_password")
    page.click("#login-button")
    error_msg = page.locator(".error-message-container").text_content()
    assert "Epic sadface" in error_msg
# ヘッドレスモードで実行(デフォルト)
pytest tests/test_saucedemo.py -v

# ブラウザを表示して実行(デバッグ時に便利)
pytest tests/test_saucedemo.py --headed

# ブラウザを指定して実行
pytest tests/test_saucedemo.py --browser firefox

⑩ よく使うコマンドまとめ

コマンド説明
pytestすべてのテストを実行
pytest -v詳細表示モード
pytest -x最初の失敗で即停止
pytest -k "キーワード"テスト名でフィルタ実行
pytest -m smokeマークでフィルタ実行
pytest -sprint文の出力を表示
pytest --lf前回失敗したテストだけ再実行
pytest -n 44並列で実行(pytest-xdist)
pytest --html=report.htmlHTMLレポート出力(pytest-html)

FAQ

Q. pytest と unittest はどちらを使うべき?

新規プロジェクトや学習目的ならpytestを強くおすすめします。記述量が少なく、エラーメッセージが読みやすく、PlaywrightやSeleniumとの相性も抜群です。既存のunittestコードはpytestでもそのまま実行できるので、移行も段階的に行えます。

Q. conftest.py はどこに置けばいい?

プロジェクトの規模によりますが、基本は tests/conftest.py に置けばOKです。サブディレクトリにも置けて、そのディレクトリ内のテストにのみ適用されます。大規模プロジェクトでは階層的に配置してスコープを分けるのがベストプラクティスです。

Q. テストが遅い場合はどうすればいい?

pytest-xdist を使って並列実行するのが最も効果的です(pytest -n auto)。また、重い初期化処理は scope="session" のfixtureにまとめてテスト実行全体で1回だけ実行するようにすると大幅に短縮できます。

Q. pytest はCI/CDでも使える?

はい、GitHub ActionsやGitLab CI、Jenkins など主要なCI/CDツールとの相性は抜群です。コマンドラインで実行できるため、YAMLの run: pytest 一行で組み込めます。HTMLレポートをアーティファクトとして保存することも簡単にできます。

⚠️ pytestでよくあるはまりポイント6選

実務でpytestを使い始めたときに多くの人がつまずくポイントをまとめました。事前に把握しておくと、無駄なデバッグ時間を大幅に減らせます。

① テストファイル・関数名が test_ で始まっていない

pytestはファイル名が test_*.py、関数名が test_ で始まるものだけを自動検出します。check_login()validate_form() のように書いてもテストとして認識されず、実行しても「collected 0 items」と表示されます。

conftest.py の置き場所を間違えてfixtureが読み込まれない

conftest.py はpytestが自動的に読み込みますが、テストファイルと同じディレクトリか、その上の階層に置く必要があります。テストファイルと別の場所に置いてもimport不要で使えると思ったら「fixture not found」エラーになる、というのがよくあるミスです。実務で一番多い原因は tests/ フォルダの外に置いてしまうケースです。

project/
├── src/
└── tests/
    ├── conftest.py   ← ここに置く(tests/直下)
    └── test_api.py

scope="session" のfixtureで状態が引き継がれてテストが干渉する

セッションスコープのfixtureはテスト実行全体で1回だけ初期化されます。そのため、あるテストがfixtureの状態を書き換えると、後続のテストに影響が出ます。特にリストや辞書などのミュータブルなデータをfixtureで返す場合は注意が必要です。特にAPIテストでレスポンスデータをfixtureに保存して書き換えると、他のテストが失敗する原因になります。

④ カスタムmarkを pytest.ini に登録しないと警告が出る

@pytest.mark.smoke のようなカスタムマークを使っていても、pytest.ini[pytest] markers = に登録しないと「PytestUnknownMarkWarning」警告が出続けます。CI環境では警告がエラー扱いになることもあるので、必ず登録しましょう。

print() の出力がターミナルに表示されない

pytestはデフォルトで標準出力をキャプチャするため、テスト内の print() が見えません。デバッグ時に確認したい場合は pytest -s オプションをつけて実行してください。pytest.iniaddopts-s を追加すると常に表示できますが、CI環境ではログが増えすぎる点に注意。なお、テストが失敗した場合は print() の出力が自動的に表示されます。

assert の前に例外が発生してテスト結果が「ERROR」になる

テストが「FAILED」ではなく「ERROR」になっているときは、assert に到達する前に予期しない例外が発生しています。fixtureの初期化失敗・importエラー・型エラーが原因であることが多いです。pytest -v --tb=long でスタックトレースを詳しく確認しましょう。

⑦ プロジェクトルート以外から実行して ModuleNotFoundError が出る

初心者が必ず一度はハマるエラーがこれです。tests/ フォルダの中に移動してから pytest を実行すると、src モジュールが見つからず ModuleNotFoundError: No module named 'src' が発生します。

# ❌ NG:testsフォルダの中から実行
cd tests
pytest

# ✅ 推奨:プロジェクトルートから実行
cd my_project
pytest

pytestは常にプロジェクトルート(pytest.ini があるディレクトリ)から実行するのが基本です。

📋 この記事のまとめ

  • pytest はシンプルな assert 文だけでテストが書ける、Python最強のテストフレームワーク
  • fixture で前処理・後処理・共有データを宣言的に管理し、コードの重複を排除できる
  • parametrize で境界値・正常系・異常系を効率よく網羅し、テストの品質を高められる
  • conftest.py に共通fixtureをまとめると複数テストファイルで再利用でき、保守性が上がる
  • mark でテストをグループ化し、スモーク・回帰・スキップを柔軟にコントロールできる
  • pytest-html でレポートを自動生成し、CI/CDや上司への報告に活用できる

pytestをマスターすれば、PlaywrightやSeleniumとの組み合わせで本格的なテスト自動化基盤を構築できます。まずはシンプルな関数テストから始めて、fixture・parametrize・conftest.py と段階的にステップアップしていきましょう。

タイトルとURLをコピーしました