Python | テスト・設計・品質:assert

Python Python
スポンサーリンク

assert って何?まずは一言でいうと

assert は、
「この条件が成り立っているはずだ」とコードに書いておくための仕組みです。

成り立っていれば何も起きない。
成り立っていなければ AssertionError を起こして、
「ここで想定外のことが起きた」と教えてくれます。

テストコードでは「期待する結果」を書く場所として、
アプリコードでは「ここは絶対こうなっている前提」を明示するために使います。


Python の assert の基本的な動き

いちばんシンプルな例

def add(a: int, b: int) -> int:
    return a + b

result = add(1, 2)
assert result == 3
Python

ここで起きていることはこうです。

result == 3 を評価する
True なら何も起きない(そのまま次の行へ)
False なら AssertionError が発生してプログラムが止まる

つまり、assert

「この条件が False になったら、そこで止まってくれ」

という“安全装置”です。

メッセージを付ける

assert にはメッセージも付けられます。

assert result == 3, "add(1, 2) は 3 になるはず"
Python

False になったときは、

AssertionError: add(1, 2) は 3 になるはず

のように表示されます。
「何を期待していたのか」をメッセージで残しておくと、
あとで自分や他人が読んだときに理解しやすくなります。


テストコードでの assert(pytest とセットで考える)

「期待する結果」を書くのが assert の役割

pytest では、assert がテストの主役です。

# calc.py
def mul(a: int, b: int) -> int:
    return a * b
Python
# test_calc.py
from calc import mul

def test_mul_simple():
    assert mul(2, 3) == 6

def test_mul_zero():
    assert mul(0, 5) == 0
Python

ここでの assert は、

mul(2, 3) の結果は 6 であるべき
mul(0, 5) の結果は 0 であるべき

という「仕様」をコードで表現しています。

pytest は、assert が失敗したときに、

実際の値
期待した値

をきれいに表示してくれます。

>       assert mul(2, 3) == 7
E       assert 6 == 7
E        +  where 6 = mul(2, 3)

この「差分を見せてくれる」のが、pytest × assert の強力なところです。

例外が起きることも assert でチェックする(pytest.raises)

例外のテストは pytest.raises を使いますが、
本質的には「例外が起きる」という条件を assert しているのと同じです。

import pytest

def div(a: int, b: int) -> float:
    if b == 0:
        raise ValueError("b must not be zero")
    return a / b

def test_div_zero():
    with pytest.raises(ValueError) as excinfo:
        div(1, 0)

    assert "b must not be zero" in str(excinfo.value)
Python

ここでは、

div(1, 0) が ValueError を投げること
そのメッセージに特定の文字列が含まれること

assert で確認しています。


アプリコードでの assert(「前提条件」を明示する)

「ここは絶対こうなっているはず」をコードに残す

テストだけでなく、アプリ本体のコードでも assert は使えます。

例えば、DB からユーザーを取ってくる関数があって、
「この時点では必ずユーザーが存在しているはず」という前提があるとします。

def get_user_name(user_id: int) -> str:
    user = find_user_by_id(user_id)
    assert user is not None, f"user_id={user_id} のユーザーが存在する前提"
    return user.name
Python

ここでの assert は、

「もし user が None だったら、それはバグだからすぐ気づきたい」

という意図をコードに埋め込んでいます。

このように、assert

「ありえないはずの状態になったら、静かに進まずに爆発してほしい」

という場所に置くと効果的です。

バリデーションとの違い

assert は「開発者の前提チェック」です。
ユーザー入力のチェックなど、「想定内のエラー」は if と例外で処理します。

# ユーザー入力のチェック(バリデーション)
if b == 0:
    raise ValueError("0 では割れません")

# 開発者の前提チェック(assert)
assert total >= 0, "合計値が負になるのはバグ"
Python

外からの入力に対しては「丁寧にエラーを返す」。
自分のコードの前提に対しては「崩れたら assert で即座に気づく」。

この使い分けが大事です。


assert の注意点(特に重要なポイント)

最適化オプションで無効化されることがある

Python は、-O(大文字のオー)オプションを付けて実行すると、
assert 文を無視する(削除する)モードになります。

python -O app.py

このモードでは、assert は実行されません。
つまり、「絶対に実行されてほしいチェック」を assert に書くのは危険です。

例えば、これはダメな例です。

assert user_is_admin, "管理者だけが実行できる処理です"
do_admin_only_operation()
Python

-O で実行すると、assert が消えてしまい、
管理者チェックなしで do_admin_only_operation() が動いてしまいます。

「セキュリティやビジネスロジックに関わるチェック」は、
必ず if と例外で書きます。

if not user_is_admin:
    raise PermissionError("管理者だけが実行できます")
Python

assert はあくまで「開発時の安全装置」として使う、
という意識を持っておくのがとても重要です。

副作用を持つ処理を assert の中に書かない

これは地味に危険なパターンです。

assert log_and_return_flag()  # 中でログを書いている
Python

log_and_return_flag() がログを書いたり、何か状態を変えたりする関数だと、
-O で assert が消えたときに、その副作用も消えてしまいます。

assert の中には「純粋な条件式」だけを書く、
副作用のある処理は外に出す、というのが安全な書き方です。


まとめ(assert は「前提と期待をコードに刻む道具」)

初心者目線で assert を整理すると、こうなります。

assert 条件 は、「この条件が True である前提」をコードに書くための文。False なら AssertionError で止まる。
テストコードでは、「期待する結果」や「期待する例外」を表現するための中心的な道具になる。
アプリコードでは、「ここは絶対こうなっているはず」という開発者の前提を明示するために使う。ただし、ビジネスロジックやセキュリティチェックには使わない。
-O オプションで assert は無効化されるので、「必ず実行されるべき処理」や副作用のある処理を assert の中に書かないことが重要。

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