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と比べて、記述量が少なく、エラーメッセージが読みやすく、プラグインエコシステムが豊富という特徴があります。
| 比較項目 | unittest | pytest |
|---|---|---|
| 記述スタイル | クラス継承が必要 | 関数ベース(クラス不要) |
| アサーション | 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) == 0setup / 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 をブラウザで開くと確認できます。テスト名・合否・実行時間・エラー詳細が一覧表示されます。
--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 -s | print文の出力を表示 |
pytest --lf | 前回失敗したテストだけ再実行 |
pytest -n 4 | 4並列で実行(pytest-xdist) |
pytest --html=report.html | HTMLレポート出力(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.ini の addopts に -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
pytestpytestは常にプロジェクトルート(pytest.ini があるディレクトリ)から実行するのが基本です。
📖 関連記事
📋 この記事のまとめ
- pytest はシンプルな
assert文だけでテストが書ける、Python最強のテストフレームワーク - fixture で前処理・後処理・共有データを宣言的に管理し、コードの重複を排除できる
- parametrize で境界値・正常系・異常系を効率よく網羅し、テストの品質を高められる
- conftest.py に共通fixtureをまとめると複数テストファイルで再利用でき、保守性が上がる
- mark でテストをグループ化し、スモーク・回帰・スキップを柔軟にコントロールできる
- pytest-html でレポートを自動生成し、CI/CDや上司への報告に活用できる
pytestをマスターすれば、PlaywrightやSeleniumとの組み合わせで本格的なテスト自動化基盤を構築できます。まずはシンプルな関数テストから始めて、fixture・parametrize・conftest.py と段階的にステップアップしていきましょう。

