🧪Pythonテストコード入門!pytestで安全なコードを書く方法を初心者向けに徹底解説

IT・テクノロジー

🧪 Pythonコードに「安心」を与えるテストの世界へ

「自分が書いたコード、本当に正しく動いてる?」そんな不安を感じたことはありませんか?プログラムが想定通りに動くかを自動でチェックしてくれる仕組みがテストコードです。一度書いてしまえば、コマンド一発で何度でも検証できる——これがあるかないかで、開発体験は劇的に変わります✨

テストコードを書く習慣が身につくと、機能追加やリファクタリングのたびに「他の部分を壊していないか」を自動で確認できるようになります。修正が怖くなくなり、コードを育てていく楽しさが何倍にもなりますよ🚀

📖 そもそもテストとは?なぜ書くのか

テストとは、プログラムが想定通りの動作をすることを確認する作業のことです。例えば毎月のスマホ代を集計するアプリで、本来は1月から12月までの合計を出すはずなのに、なぜか1月から11月までしか合計されていない——こんな不具合(バグ)を未然に防ぐのがテストの役割です。

テストで確認するのは結果の正しさだけではありません。処理に時間がかかりすぎていないか、想定した人数の同時アクセスに耐えられるか、といったパフォーマンス・負荷の観点も含まれます。「絶対にバグのない完璧なコードが書ける人」はいないので、テストはすべての開発者に必要な技術と言えます。

🎯 テストの3つの種類

テストは大きく分けて次の3種類があります。

  • 🔬 単体テスト(ユニットテスト):関数やメソッド単位で動作を確認する最小単位のテスト
  • 🔗 結合テスト:複数の関数やモジュールを組み合わせた動作を確認するテスト
  • 🖥️ システムテスト:実際の利用シーンを想定して、画面操作からアウトプットまで通しで確認するテスト

今回扱うのは、もっとも基本かつ頻繁に書くことになる単体テストの領域です。

📦 pytestを使う準備

Pythonでテストコードを書くなら、デファクトスタンダードのライブラリが pytest です。標準ライブラリではないので、まずはインストールから始めましょう。

pip install pytest

📁 ディレクトリ構成

テストしたい関数 sum_numberscode.py に書かれているとします。これを src ディレクトリに置き、同じ階層に tests ディレクトリを作って、その中にテストコードを書く test_code.py を用意します。

  • 📂 src/code.py — テスト対象の関数を書くファイル
  • 📂 tests/test_code.py — テストコードを書くファイル
  • 📄 src/__init__.pytests/__init__.py — 中身は空でOK。これがないとテスト実行時にエラーになります

🌱 はじめてのテストコードを書いてみよう

偶数の合計と奇数の合計を返すシンプルな関数 sum_numbers を例にします。

# src/code.py
def sum_numbers(a, b):
    return a + b

これに対するテストコードはこうです。

# tests/test_code.py
from src.code import sum_numbers

def test_sum_numbers():
    result = sum_numbers(1, 2)
    assert result == 3

ポイントは2つ。テスト関数の名前は test_ で始めることと、assert 文で「期待した結果と一致するか」を書くことです。assert の後ろの条件が True ならテスト成功、False なら失敗となります。

▶️ テストの実行方法

コマンドラインでプロジェクトのルートに移動して、次のように実行します。

pytest tests/test_code.py

成功すれば「1 passed」と表示され、失敗すれば「1 failed」とともに、どの assert 文がどんな値で失敗したかが詳しく表示されます。例えば assert result == 4 と間違って書いていれば、「assert 3 == 4」のように実際の値が一目でわかるので、デバッグも楽々です🔍

📚 複数のassert・複数の戻り値

1つのテスト関数に assert 文を複数並べることもできます。例えば関数が2つの値をタプルで返す場合、それぞれの値を別々に検証できます。テストファイルが増えてきたら、ファイル名を指定せず単に pytest と打つだけで、配下のテストすべてを一気に実行することも可能です。

🎭 外部システムをモック化する:monkeypatch

実務では「テストしたい関数の中で、外部APIを呼び出している」というケースがよくあります。例えばユーザーIDを受け取って、外部サーバーから名前を取得して辞書を返す関数を考えてみましょう。

# src/code.py
import requests

def get_json_data(user_id):
    res = requests.get(f"https://api.example.com/users/{user_id}")
    return res.json()

def get_username_dict(user_ids):
    result = {}
    for user_id in user_ids:
        data = get_json_data(user_id)
        result[user_id] = data["name"]
    return result

このまま get_username_dict をテストすると、外部APIに実際にアクセスしてしまいます。サーバーが落ちていたらテストが失敗するし、毎回ネットワーク通信が走るのも困りますよね😓

🪄 モックで外部依存を置き換える

こんなときに使うのが モック(mock)です。pytestの monkeypatch という仕組みを使うと、特定の関数を一時的に別のダミー関数に差し替えられます。

# tests/test_code.py
from src import code
from src.code import get_username_dict

def get_json_data_mock(user_id):
    return {"name": "サップ"}

def test_get_username_dict(monkeypatch):
    monkeypatch.setattr(code, "get_json_data", get_json_data_mock)
    result = get_username_dict([1, 2])
    assert result == {1: "サップ", 2: "サップ"}

monkeypatch.setattr の3つの引数で、「どのモジュールの・どの関数を・何に置き換えるか」を指定します。これで get_username_dict の中で呼ばれる get_json_data は、外部通信せず常に固定の辞書を返すダミー関数に差し替えられ、安定したテストが可能になります🎯

⚠️ 例外を投げる関数のテスト

「不正な入力が来たらきちんと例外を投げてくれるか」も、テストで確認したい重要なポイントです。例えば名前が None なら ValueError を投げる関数を見てみましょう。

# src/code.py
def username_validation(username):
    if username is None:
        raise ValueError("名前が設定されていません")

これをテストするには、pytestの pytest.raiseswith 文で使います。

import pytest
from src.code import username_validation

def test_username_validation():
    with pytest.raises(ValueError) as e:
        username_validation(None)
    assert str(e.value) == "名前が設定されていません"

with pytest.raises(ValueError) のブロック内で指定した例外が発生すればテスト成功、それ以外の例外(または例外が発生しない場合)はテスト失敗となります。e.value から例外メッセージにもアクセスできるので、メッセージ内容まで含めて検証できるのが便利です。

🛡️ テストを書くと得られる本当のメリット

テストコードの真価は「最初の動作確認」ではなく、機能追加やリファクタリングをした後に発揮されます。コードを変更したとき、変更していない既存機能まで壊してしまっていないか——これを目視で確認するのは膨大な労力ですが、pytest コマンド一発で全自動チェックできます🚀

「テストを書く時間がない」と感じるかもしれませんが、後から手動で動作確認する時間と比べたら圧倒的に節約になります。複雑で重要な機能から優先的にテストを書く、というバランス感覚で取り入れるのがおすすめです。

📚 Pythonテスト・品質向上のためのおすすめ書籍

テストコードは一度習得すれば、一生モノのスキルになります。体系的に学べる書籍を手元に置いておくと、学習効率が一気に上がりますよ✨

📖 Pythonテスト駆動開発の決定版

pytestの基本から実践的なテクニックまで網羅的に解説。フィクスチャ、パラメタライズ、モックなど、現場で使う機能を順を追って学べます。

🐍 堅牢なPythonコードを書くための1冊

型ヒント・テスト・例外設計など、「壊れにくいコード」を書くためのプラクティスを徹底解説。中級者へのステップアップに最適です。

📕 Pythonの“良い書き方”が身につく定番書

テスト容易性の高いコード設計や、モックの使いどころなど、Python開発者が押さえるべき90項目が凝縮されています。

⌨️ 学習効率を上げる作業環境アイテム

長時間のコーディング・テスト記述でも疲れにくいキーボードは、学習継続の心強い味方になります。

テスト対象のコードとテストコードを並べて見られるデュアルディスプレイ環境は、開発効率を一気に底上げしてくれます。

❓ よくある質問(FAQ)

🤔 Q1. テストコードはどこまで書けばいい?

A. 「すべての関数に100%のテストを書く」のは現実的ではありません。複雑なロジックや重要な機能から優先的にテストを書くのが現実的です。テストにかかる時間と、バグが発生したときの影響度を天秤にかけて判断しましょう。

🤔 Q2. unittestとpytest、どちらを使うべき?

A. unittest はPython標準ライブラリですが、現代ではより簡潔に書ける pytest が主流です。記述量が少なく、失敗時の表示もわかりやすいため、特別な理由がなければpytestを選ぶのがおすすめです。

🤔 Q3. テストファースト(TDD)で開発するべき?

A. テスト駆動開発(TDD)はテストを先に書いてから実装する手法で、コードの設計が良くなるメリットがあります。ただし慣れが必要なので、まずは「実装後にテストを書く」習慣から始めて、慣れてきたらTDDに挑戦するのがスムーズです。

🤔 Q4. クラスのメソッドもpytestでテストできる?

A. もちろん可能です。テストコード内でクラスのインスタンスを作成し、メソッドを呼び出して assert 文で検証するだけ。関数のテストとほぼ同じ流れで書けます。

🤔 Q5. __init__.py がないとどうしてエラーになるの?

A. Pythonがディレクトリを「パッケージ」として認識するための目印が __init__.py です。これがないと、from src.code import ... のようなインポートが解決できず、テスト実行時にエラーになります。空ファイルでも構いませんので必ず置きましょう。

🎯 まとめ:テストコードはPython開発者の必須スキル

pytestを使えば、assert 文を書くだけのシンプルなコードで「想定通りに動くか」を機械的に確認できるようになります。さらに monkeypatch で外部依存をモック化したり、pytest.raises で例外を検証したりと、実務で必要な機能が一通り揃っています🛠️

最初の1行から始めれば、徐々にテストの書き方が体に馴染んできます。今日学んだ内容を元に、自分の書いた小さな関数からでも test_ 関数を書いてみてください。「変更しても壊れていない」と自信を持って言える開発体験は、一度味わうともう手放せませんよ✨

コメント

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