【入門編】pytestとpdbを組み合わせた自動テスト中のデバッグ戦略 – デバッグ・コード品質・テストツール生産性向上バイブル

こんにちは!日々のPython開発、本当にお疲れ様です。

突然ですが、皆さんはテストコード(pytest)を書いていて、こんな絶望感を味わったことはありませんか?

> 「あれ……? テストが緑(成功)になるはずなのに、なんでこのテストだけ赤(失敗)になるんだ? どこで値が狂った?」
>
> `print()` デバッグを仕込んではテストを走らせ、また仕込んでは走らせ……の無限ループ。
> ターミナルに流れる長大なトレースバック(エラーログ)と睨めっこ。

これをやっていると、貴重な開発時間が溶けていくだけでなく、何より疲弊してしまいますよね。

大丈夫。今日、この瞬間からその悩みとはお別れです。
世界中のプロフェッショナルなPythonエンジニアが密かに使っている「pytest × pdb(IPdb)の自動アタッチ戦略」をマスターすれば、あなたのデバッグライフは劇的に、そして圧倒的に楽になります。

今回は、まだデバッガの扱いに慣れていない初心者の方に向けて、その本質から実務で即座に使える極意まで、親身になって優しく解説していきますね。

—

1. そもそも `pdb` / `IPdb` とは何をするものなのか?

Python標準ライブラリには、`pdb`(Python DeBugger)という強力なデバッガが最初から組み込まれています。また、その機能をさらにリッチにし、シンタックスハイライトやタブ補完を効かせた`IPdb`という上位互換ツールも存在します。

これらが何をしてくれるかと言うと、一言で言えば「コードがバグで死んだ瞬間(あるいは任意の行)、その場でプログラムを完全にフリーズさせ、生きたままの変数や処理を自分の手で操作できるようにするタイムマシン」です。

`print()` デバッグが「現場の写真をあとから眺める」のだとしたら、`pdb` は「事件が起きた瞬間の現場に、自分がタイムリープして犯人に直接話を聞きに行く」ようなもの。圧倒的な情報の密度とスピードの差があります。

まずはインストール(現代の標準装備)

標準の `pdb` でも十分ですが、今回は圧倒的に見やすく使いやすい `IPdb` を使います。以下のコマンドでサクッとインストールしておきましょう。

pytest本体と、色鮮やかで使いやすいipdbをインストールします
pip install pytest ipdb

—

2. 【基本のキ】`–pdb` オプションで「テスト失敗時に即座に止める」

まずは、一番簡単かつ最強の機能から紹介します。
あなたが書いたテストコードが失敗したとき、pytestに `–pdb` という魔法のオプションを渡すだけです。

実験用のサンプルコードを作ってみましょう

例えば、次のような「引数を2倍にするだけのシンプルな関数」と、それをテストするコードがあるとします。

`calc.py`(実装コード)

def double(x):
# うっかり文字列が渡されたときのことを想定していないバグを埋め込んでおきます
return x 2

`test_calc.py`(テストコード)

from calc import double

def test_double():
# 整数なら当然うまくいく
assert double(3) == 6

# では、文字列の “3” を渡したらどうなるか?(本当は “33” になってほしいとする)
# ここであえて間違った期待値(6)を設定してテストを失敗させてみます
assert double(“3”) == 6

実行してみよう!

このテストを、通常の `pytest` ではなく、`–pdb` オプション付きで実行してみてください。

pytest –pdb test_calc.py

実行ログの流れ(イメージ):

============================= test session starts ==============================
platform linux — Python 3.10.x, pytest-7.x.x, pluggy-1.x.x
rootdir: /path/to/project
collected 1 item

test_calc.py F [F]

================================== FAILURES ===================================
_________________________________ test_double _________________________________

def test_double():
assert double(3) == 6
> assert double(“3”) == 6
E AssertionError: assert ’33’ == 6
E +- where ’33’ == 6

> /path/to/calc/test_calc.py(7)test_double()
-> assert double(“3”) == 6
(Pdb)

おっ、何やら見慣れない `(Pdb)` というプロンプト(入力待ち)が出てきましたね!
テストが失敗したまさにその行(`test_calc.py` の 7行目)で、Pythonの実行がガッチリと一時停止しています。

停止した世界で何ができるのか?(Pdbの基本操作)

この停止状態(対話モード)に入ったら、以下のようなコマンドが使えます。

  • `p 変数名` (printの略):その時点での変数の値を確認する
  • `c` (continueの略):デバッグを終了し、プログラムの実行を続ける
  • `q` (quitの略):デバッグを即座に強制終了する

実際にプロンプトに打ち込んでみましょう。

(Pdb) p double(“3”)
’33’

「なるほど! `double(“3”)` を実行すると、数値の `6` じゃなくて文字列の `’33’` が返ってくるから `assert` で弾かれたんだな!」と、一瞬で原因が特定できましたね。

確認ができたら、`q` を押してデバッガを抜け出しましょう。

(Pdb) q

これだけでもう、`print()` デバッグに戻れなくなるほどの快適さを実感できたはずです。

—

3. 【実務の知恵】特定のテストケースだけを正確にピンポイントで止める

開発が進み、テストコードが何百、何千と増えてくると、すべてのテストで `–pdb` が発動してしまうと、逆にテンポが悪くなってしまいますよね。

「このモジュールの、この特定のテストケースの、この瞬間だけをデバッグしたい!」

そんなときは、コード内に直接ブレークポイントを埋め込むアプローチを使います。Python 3.7以降であれば、特別なライブラリのインポートすら不要です。

`breakpoint()` を仕込む

先ほどのテストコードに、Python標準の `breakpoint()` を挟んでみましょう。

`test_calc.py`(修正版)

from calc import double

def test_double_advanced():
val = “3”

# Python 3.7+ ならこの一行を書くだけで、自動的にIPdb(またはpdb)が起動します
breakpoint()

result = double(val)
assert result == 6

この状態で、通常の `pytest` コマンドでテストを走らせてみます。

pytest test_calc.py

すると、エラーの有無に関わらず、`breakpoint()` を書いた行に到達した瞬間に、ピタッと処理が止まり、IPdbの画面が立ち上がります。
テスト全体の流れの中で、「あ、ここ怪しいな」と思った場所にスイスイと罠(ブレークポイント)を仕掛けられる。これがプロの技です。

—

4. 【CI/CDの罠と回避策】CI環境で `–pdb` を使うときの注意点

ここで、現場のシニアエンジニアとして一つ重要な知見を共有しておきます。

GitHub ActionsやGitLab CIなどの CI/CDパイプライン(自動テスト環境) で pytest を走らせる際、 `–pdb` オプションをうっかりつけっぱなしにすると、ビルドが永遠に終わらなくなります(ハングアップします)。

なぜなら、CI環境には「人間(開発者)」が存在せず、`(Pdb)` プロンプトに対してコマンドを入力してあげることができないため、バックグラウンドでテストが永遠にユーザーからの入力を待ち続けてしまうからです。

対策:CI環境ではデバッガを無効化する

そのため、ローカル環境では `–pdb` を活用しつつ、CI環境では安全に無視させる(あるいはエラーとして即座に落とす)構成にするのがベストプラクティスです。

`pytest.ini` または `pyproject.toml` という設定ファイルに、デフォルトの挙動を定義しておきましょう。

`pyproject.toml` の設定例

[tool.pytest.ini_options]
テストの基本動作を定義するセクションです
addopts = “–strict-markers”
※注意: addopts に常時 –pdb を入れるのはローカル開発のみに留め、
CI環境では環境変数などで動的に制御するか、コマンドラインから明示的に呼び出すようにしましょう。

—

まとめ:毎日のコーディングが劇的に楽になる

いかがでしたでしょうか?

  • `pytest –pdb` を使えば、テストが失敗した「現場」にタイムリープできる。
  • `breakpoint()` を使えば、怪しい箇所をピンポイントで狙い撃ちして停止できる。
  • `print()` を何行も書いてコードを汚す必要はもうない。

これをマスターしたあなたの手元には、バグを恐れる理由が一つ減ります。
「動かない? じゃあデバッガで中身を覗いてみよう」——そう思える余裕が生まれたとき、あなたのエンジニアとしての成長速度は跳ね上がります。

ぜひ、次の開発からあなたのプロジェクトにこの戦略を取り入れてみてください。毎日のコーディングが、きっと驚くほど楽しく、軽快になりますよ!

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