テストとデバッグ
Python
pytest・unittest・logging・pdb
pytest の基本
テスト関数・アサーション・フィクスチャ
import pytest
# === 基本的なテスト ===
def test_add():
assert 1 + 1 == 2
def test_string():
s = 'hello'
assert s.upper() == 'HELLO'
assert 'ell' in s
assert s.startswith('h')
# 例外のテスト
def test_raises():
with pytest.raises(ZeroDivisionError):
1 / 0
with pytest.raises(ValueError, match='invalid'):
int('abc')
# === フィクスチャ ===
@pytest.fixture
def sample_user():
return {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}
def test_user_name(sample_user):
assert sample_user['name'] == 'Alice'
# スコープ付きフィクスチャ
@pytest.fixture(scope='module') # session, package, class, function
def db_connection():
conn = create_db_connection()
yield conn # テスト後の後片付け
conn.close()
# === パラメータ化 ===
@pytest.mark.parametrize('input, expected', [
(2, 4),
(3, 9),
(4, 16),
(-1, 1),
])
def test_square(input, expected):
assert input ** 2 == expected
# 複数パラメーター
@pytest.mark.parametrize('a, b, expected', [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
])
def test_add(a, b, expected):
assert a + b == expectedモック・パッチ
unittest.mock・pytest-mock
from unittest.mock import Mock, MagicMock, patch, call
import pytest
# === Mock の基本 ===
mock = Mock()
mock.method() # 呼び出し可能
mock.method.return_value = 42 # 戻り値を設定
mock.method() # 42
mock.method.assert_called_once() # 1回呼ばれたことを確認
mock.method.assert_called_with(1, 2) # 引数も確認
mock.method.assert_not_called()
mock.method.call_count # 呼び出し回数
# 副作用
mock.side_effect = ValueError('error') # 例外を発生
mock.side_effect = [1, 2, 3] # 呼ぶたびに返す値
# === patch ===
# 関数・クラス・属性を一時的に置き換える
# デコレーター
@patch('mymodule.requests.get')
def test_api(mock_get):
mock_get.return_value.json.return_value = {'id': 1}
result = mymodule.fetch_user(1)
assert result['id'] == 1
mock_get.assert_called_once_with('https://api.example.com/users/1')
# コンテキストマネージャー
def test_file_read():
with patch('builtins.open', Mock(return_value=MockFile())):
result = read_config('config.txt')
assert result == {'key': 'value'}
# pytest-mock(mockerフィクスチャ)
def test_with_mocker(mocker):
mock_get = mocker.patch('mymodule.requests.get')
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = {'data': []}
result = mymodule.fetch_all()
assert result == []ロギングとデバッグ
logging・pdb・型チェック・プロファイリング
import logging
# 基本設定
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
)
# ロガーの取得(モジュール名で階層化)
logger = logging.getLogger(__name__)
# ログレベル(低→高): DEBUG < INFO < WARNING < ERROR < CRITICAL
logger.debug('詳細なデバッグ情報')
logger.info('一般的な情報')
logger.warning('警告(処理は続行)')
logger.error('エラー(処理に問題)')
logger.critical('致命的エラー')
# 例外のスタックトレース付き
try:
risky()
except Exception:
logger.exception('予期しないエラー') # exc_info=True と同等
# ファイルへの出力
handler = logging.FileHandler('app.log', encoding='utf-8')
handler.setLevel(logging.WARNING)
logger.addHandler(handler)
# structlog(構造化ログ)
import structlog
log = structlog.get_logger()
log.info('ユーザーログイン', user_id=42, ip='192.168.1.1')
# pdb(デバッガー)
import pdb; pdb.set_trace() # ブレークポイント(旧来)
breakpoint() # Python 3.7+(推奨)
# コマンド: n(next) s(step) c(continue) l(list) p(print) q(quit)
# プロファイリング
import cProfile
cProfile.run('my_function()', sort='cumulative')
# timeit(マイクロベンチマーク)
import timeit
timeit.timeit('[x**2 for x in range(1000)]', number=1000)