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爆速講座