Pythonユニットテスト完全ガイド – 初心者から上級者まで実践的テスト手法
Pythonでの開発において、品質の高いコードを維持するためにユニットテストは必須のスキルです。本記事では、Pythonユニットテストの基礎から応用まで、実践的な手法を網羅的に解説します。
ユニットテストとは
ユニットテストは、プログラムの最小単位(関数やメソッド)が期待通りに動作するかを検証するテスト手法です。バグの早期発見、リファクタリングの安全性確保、コードの品質向上に大きく貢献します。
Pythonの主要テストフレームワーク
1. unittest(標準ライブラリ)
Pythonに標準で含まれるテストフレームワークです。
import unittest
def add(a, b):
return a + b
class TestMath(unittest.TestCase):
def test_add(self):
self.assertEqual(add(2, 3), 5)
self.assertEqual(add(-1, 1), 0)
if __name__ == '__main__':
unittest.main()
2. pytest(推奨)
シンプルで強力な外部ライブラリです。
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
3. doctest
ドキュメント内のテストを実行できます。
def multiply(a, b):
"""
>>> multiply(2, 3)
6
>>> multiply(0, 5)
0
"""
return a * b
if __name__ == "__main__":
import doctest
doctest.testmod()
基本的なテストパターン
正常系テスト
def divide(a, b):
return a / b
def test_divide_normal():
assert divide(10, 2) == 5.0
assert divide(7, 2) == 3.5
異常系テスト
import pytest
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
divide(10, 0)
境界値テスト
def is_adult(age):
return age >= 18
def test_is_adult_boundary():
assert is_adult(17) == False
assert is_adult(18) == True
assert is_adult(19) == True
テストデータの管理
フィクスチャ(pytest)
import pytest
@pytest.fixture
def sample_data():
return {"name": "太郎", "age": 25}
def test_user_data(sample_data):
assert sample_data["name"] == "太郎"
assert sample_data["age"] == 25
setUp/tearDown(unittest)
import unittest
class TestDatabase(unittest.TestCase):
def setUp(self):
self.db = {"users": []}
def tearDown(self):
self.db.clear()
def test_add_user(self):
self.db["users"].append("太郎")
self.assertEqual(len(self.db["users"]), 1)
パラメータ化テスト
pytest.mark.parametrize
import pytest
@pytest.mark.parametrize("input,expected", [
(1, 2), (2, 4), (3, 6), (0, 0)
])
def test_double(input, expected):
assert input * 2 == expected
unittest.subTest
import unittest
class TestMultiply(unittest.TestCase):
def test_multiply_cases(self):
cases = [(2, 3, 6), (0, 5, 0), (-1, 3, -3)]
for a, b, expected in cases:
with self.subTest(a=a, b=b):
self.assertEqual(a * b, expected)
モックとスタブの活用
外部APIのモック
from unittest.mock import patch
import requests
def get_weather(city):
response = requests.get(f"http://api.weather.com/{city}")
return response.json()["temperature"]
@patch('requests.get')
def test_get_weather(mock_get):
mock_get.return_value.json.return_value = {"temperature": 25}
result = get_weather("Tokyo")
assert result == 25
クラスメソッドのモック
from unittest.mock import Mock
class Calculator:
def add(self, a, b):
return a + b
def test_calculator_mock():
calc = Calculator()
calc.add = Mock(return_value=10)
result = calc.add(3, 7)
assert result == 10
calc.add.assert_called_once_with(3, 7)
テストカバレッジの測定
coverageライブラリの使用
# インストール
pip install coverage
# テスト実行とカバレッジ測定
coverage run -m pytest
coverage report
coverage html
基本的なカバレッジ例
def process_data(data):
if data > 0:
return data * 2
else:
return 0
def test_process_data_positive():
assert process_data(5) == 10
def test_process_data_negative():
assert process_data(-3) == 0
def test_process_data_zero():
assert process_data(0) == 0
実践的なテスト設計
AAA(Arrange-Act-Assert)パターン
def test_user_registration():
# Arrange(準備)
user_data = {"name": "太郎", "email": "taro@example.com"}
# Act(実行)
result = register_user(user_data)
# Assert(検証)
assert result["status"] == "success"
assert result["user_id"] is not None
テストの独立性
import pytest
class TestUserService:
def test_create_user(self):
user = create_user("太郎")
assert user.name == "太郎"
def test_delete_user(self):
user = create_user("花子")
delete_user(user.id)
assert get_user(user.id) is None
テスト実行とCI/CD連携
pytest設定ファイル(pytest.ini)
[tool:pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short
GitHub Actionsでの自動テスト
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Run tests
run: |
pip install pytest
pytest
テストのベストプラクティス
1. テストの命名規則
- テスト関数名は
test_
で始める - テスト内容が分かりやすい名前を付ける
test_should_return_error_when_invalid_input
のような形式
2. テストの構造化
class TestUserValidation:
def test_valid_email(self):
assert validate_email("test@example.com") == True
def test_invalid_email(self):
assert validate_email("invalid-email") == False
3. テストデータの管理
# conftest.py
import pytest
@pytest.fixture
def valid_user_data():
return {
"name": "太郎",
"email": "taro@example.com",
"age": 25
}
よくある落とし穴と対処法
テストの依存関係
# 避けるべき:テスト間の依存
class TestBad:
user_id = None
def test_create_user(self):
self.user_id = create_user("太郎")
def test_update_user(self):
update_user(self.user_id, "次郎") # 前のテストに依存
# 推奨:独立したテスト
class TestGood:
def test_update_user(self):
user_id = create_user("太郎")
result = update_user(user_id, "次郎")
assert result.name == "次郎"
過度なモック使用
# 避けるべき:すべてをモック
def test_calculate_total_bad(mocker):
mocker.patch('math.add')
mocker.patch('math.multiply')
# 実際の計算ロジックがテストされない
# 推奨:必要な部分のみモック
def test_calculate_with_discount():
# 外部APIのみモック、計算ロジックは実際にテスト
with patch('get_discount_rate') as mock_discount:
mock_discount.return_value = 0.1
result = calculate_total([10, 20, 30])
assert result == 54 # (10+20+30) * 0.9
まとめ
Pythonユニットテストは、品質の高いソフトウェア開発に欠かせない技術です。基本的なアサーション、モック、フィクスチャの使い方をマスターし、AAA パターンやテストの独立性を意識することで、保守性の高いテストコードが書けるようになります。
継続的な学習と実践を通じて、テスト駆動開発(TDD)やビヘイビア駆動開発(BDD)などの上級テクニックにも挑戦してみてください。効果的なユニットテストは、開発効率の向上とバグの削減に大きく貢献します。
■プロンプトだけでオリジナルアプリを開発・公開してみた!!
■AI時代の第一歩!「AI駆動開発コース」はじめました!
テックジム東京本校で先行開始。
■テックジム東京本校
「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。
<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。
<月1開催>放送作家による映像ディレクター養成講座
<オンライン無料>ゼロから始めるPython爆速講座