# assert 文ってなに?

assert 文は簡単にテストをする方法を提供してくれます。 書式は、次の通りで、「条件」が False になると AssertionError が発生します。

# assert 条件,  エラー文
assert False, '失敗しました'
>>> # assert 条件,  エラー文
>>> assert   False, '失敗しました'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: 失敗しました
>>> 

assert 文は、プログラム内にデバッグ用アサーション (debugging assertion) を仕掛けるための便利な方法です:
7.3. assert 文 ... 7. 単純文 (simple statement)

ちなみに True のときはなにも起こりません。

assert True, 'なにも起こらない'
>>> assert True, 'なにも起こらない'
>>> 

if 文は True だと処理を実行しますが、assert 文は True だとなにもしません。 if 文は False だとなにもしませんが、assert 文は False だと例外を投げます。


assert 文と if 文の違い
True False
if 文 処理を実行 なにもしない
assert 文 なにもしない 例外を投げる
Hello, world!

# 1. いつ使うの?

関数の外で「テスト」に使う場合と、関数の中で「動作確認」で使う場合があります。

# 1.1. 関数の外で「テスト」に使う

ざっくり言うと、テストしたい関数とテストコードを分ける書き方をします。 例えばここで足し算をする関数 add を定義したかったとします。

def add(a, b):
    c = a + b
    return c

しかし、うっかり + 1 と書き込んでしまいました。 テストで弾いて見ましょう。

# テストしたい関数
def add(a, b):
    c = a + b + 1  # <--- + 1 を書き込んでしまった。
    return c

# テストコード
assert add(1, 1) == 2, '1 + 1 が 2 ではありません。'
>>> assert add(1, 1) == 2, '1 + 1 が 2 ではありません。'
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AssertionError: 1 + 1 が 2 ではありません。
>>> 

# 1.2. 関数の中で「動作確認」に使う

上記では、テストしたい関数とテストコードが分かれていました。 一方で関数の中で途中結果が、正しいかどうかを「動作確認」するためにも使います。

うっかり関数 add に文字列 '1' を与えてしまいました。 assert 文で弾いて見ましょう。

#
# コピペで動きます
#
def add(a, b):
    assert isinstance(a, int) and isinstance(b, int),\
    '引数は int 型です。'
    c = a + b

add(1, '1')
>>> add(1, '1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in add
AssertionError
>>> 

ちなみにバックスラッシュ \ を使うと長い行を改行することができます。 PEP 8 では避けるように書かれていますが...

長い行を折り返す好ましい方法は、Python が括弧やブラケット、波括弧の中では暗黙のうちに行を継続させることを利用することです。長い行は文を括弧で囲むことで、複数行に分割できます。行を継続させるには、バックスラッシュよりもこれらを使用すべきです。
1 行の長さ - PEP 8

例外的に assert 文では使った方が好ましいよ、と書かれています。

バックスラッシュを使うのが好ましい別のケースとして assert 文を使う場合が挙げられます。
1 行の長さ - PEP 8

# ◯ ここから先は

前半は「テスト」で使う assert 文を見ていきます。 後半は「動作確認」で使う assert 文を見ていきます。

assert 文の2つの用途
場所 用途
関数の外 テスト
関数の中 動作確認
前半 - 関数の外で「テスト」に使う

# 2. 全部テストをしないといけないの?

たまに入門書を手にとって読んでいると全部テストすることが、 必要ですとか書けば書くほど良いいです。 と書いてあったりして、一瞬戸惑います。

Django の公式チュートリアルでも 最初の方にテストに関するページがでてきて詰まりました。 必要性が理解できないものを勉強するのは、苦心します。 どこを力点に置いて掘り下げていけばいいかわからないからです。

慣れてない段階だと、 全てテストしろってことは 1 + 1 == 2 とかもテストしないといけないの? みたいなことにもなりかねないのかなと思ったり、思わなかったりします...

assert 1 + 1 == 2

実は大人もね1+1がなんで2になるかわからないんだよ。 そういうのは大学で習うんだ。 だけどみんなわかったふりして1+1=2だって なんとなく覚えてるだけなんだ。
「1+1がわからない…」強迫神経症になった少女を救った、たった一つの言葉

深夜に通信機器の設定を作成する VBA マクロを組んでいたときに、 このマクロは、なにをもって正しいと判断すればいいんだ? みたいなのを悩んだりしてました。

正直なにが答えかは今もあまりわかっていません。 テスト技法とかの書籍とか買っていつかちゃんと勉強したいです。 このあたりは1つ指針になるのかな...

短くまとめると、プライベートなメソッドのテストを書く必要は 無い と考えています。 ほとんどのプライベートメソッドはパブリックメソッド経由でテストできるからです。 プライベートメソッドは実装の詳細であり、自動テストのターゲットとなる「外部から見た振る舞い」ではありません。
プライベートメソッドのユニットテストは書かないもの?

個人の趣味でやっているときは、いらないかなと... 手動で動作確認することが面倒になった時が、 テストを学ぶタイミングかなと思ったりします。 個人の趣味の時はですが...

たまたま Yahoo! 知恵袋の Python カテゴリを覗いていて そんな質問があったので記載しました。

その方はプログラミング初学者なのに Python の公式ドキュメントのチュートリアルと Django の公式ドキュメントのチュートリアルで学習を進められていました。

自信を喪失されてましたが、逆にあの公式ドキュメントで、 よくあそこまでたどり着いたなと思ってしまいました笑

以下の記事は、テストの全体像について書かれています。

以下の記事は、いろんな人のテストに対する考え方、温度感が記載されています。

# 3. 2つのテストツール








テストのための2つのツールを発見しました。 最初はサクッと作れる pytest でいいんじゃないかなと思ったりもします。

# 3.1. pytest

たくさん assert 文で書いたテストができたら pytest でまとめ上げると便利かもしれません。最近、知りました。



# 3.2. unittest

pytest は pip install pytest としないと使えません。 unittest は「標準ライブラリ」なので、最初から使うことができます。

pytest は assert 文をつぎはぎして書けるのに対して unittest は、少しもっさりした印象です。

最初は、なんでわざわざクラスを書かないといけないのか? 命名規則が PEP 8 に反するだったりするのはなんで?と色々と不思議だったのですが、 おそらく Java のフレームワークである JUnit から触発されたものだからかなと思います。

unittest ユニットテストフレームワークは元々 JUnit に触発されたもので、 他の言語の主要なユニットテストフレームワークと同じような感じです。
26.4. unittest - 標準ライブラリ

全然、わかっていないのですが Java は、 クラスに属したメソッドしか定義できないため、 そのフレームワークである JUnit に触発されたら自動的に Python の unittest もテスト関数をクラスに属したものにするような設計になったのでしょうか...

もしかしたらクラスに所属させておいた方が、もっと複雑なことが色々できるのかもしれません。 最初はペチペチ作れる pytest でいいんじゃないかなと思ったりもします。

# ◯ 調査

国外では pytest の方が使われているみたいです...

テストフレームワーク - 2018 年 Python 開発者調査結果
Testing Frameworks - Python Developers Survey 2018 Results

首位のユニットテストフレームワークは pytest で、それを unittest が追いかけています。
The leading unit-testing framework is pytest followed by unittest.

他のテストフレームワークは、この2つに比べればほとんど人気がありません。
The other unit testing frameworks are far less popular.

35% の Python ユーザはテストフレームワークを使っていません、 おそらく自分で書いたコードをテストしていないのでしょう。
It’s quite surprising that 35% of Python users don’t use any testing frameworks and are presumably not testing their code.

"Python の仮想環境を作るツール" では、 5 人に 1 人の Python ユーザが仮想化ツールを使っていませんでした、 テストフレームワークを使わないというのも、 また1つのベストプラクティスなのかもしれません(訳者注: かなり意訳しました)。
In the “Tools to create isolated Python environments” section we identified that around 1 in 5 Python users don’t use Python isolation which is another best practice.

後半 - 関数の中で「動作確認」に使う

# 4. 遅くならないの?

条件判定のために計算します。その分だけ、遅くなります笑 ちょっとした条件判定なら、気にならないかもしれません。 しかし assert で条件判定したい処理が重かったら、その分だけ遅くなってしまいます。

例えばソート関数 sort を自作したとします(ソートを自作することはないと思うのですが、良い例が思い浮かびませんでした... orz)。 結果が常に正しいかどうか判定する is_sorted という関数を作ったとします。

def sort(lst):
    n = len(lst)
    for i in range(n):
        for j in range(i):
            if lst[i] < lst[j]:
                lst[i], lst[j] = lst[j], lst[i]
    assert is_sorted(lst)

def is_sorted(lst):
    # 全ての要素について左より右が大きい
    return all(
        lst[i] <= lst[i + 1]
        for i in range(len(lst) - 1)
    )

lst = [4, 3, 1, 0, 2]
sort(lst)
lst
>>> lst
[0, 1, 2, 3, 4]
>>> 

is_sorted を実行する分だけ、処理は遅くなります。 これくらいなら気にもなりませんが、重たい条件を確認したいようなケースもあるかなと思います。 そのため assert 文は、運用時は、外せるようになっています。

# 4.1. 最適化オプション -O

実行時に最適化オプション -O を 指定することで assert 文は実行されなくなります。

$ python -O
>>> assert 1 + 1 == 0, 'This is error.'
>>> # 何も起こらない。

-O
assert 文を... 取り除く。 コンパイルされたファイル(バイトコード)のファイル名に .pyc 拡張子の前に .opt-1 を付与する(PEP 484 参照のこと)。 PYTHONOPTIMIZE を参照のこと。 Remove assert statements and any code conditional on the value of __debug__. Augment the filename for compiled (bytecode) files by adding .opt-1 before the .pyc extension (see PEP 488). See also PYTHONOPTIMIZE. 1.1.3. その他のオプション - Python のセットアップと利用

"any code conditional on the value of __debug__" ってなんだろう... 訳せませんでした...

# 4.2. 定数 __debug__

また、-O を指定した時、 組み込み定数 __debug__ には False が代入されます。

$ python -O
>>> if __debug__:
...     print('Hello, world!')
...
>>> # 何も表示されない

__debug__ - 3. 組み込み定数
この定数は、Python が -O オプションを有効にして開始されたのでなければ真です。 assert 文も参照して下さい。

# ◯ 請注意

開発する際のコードで満たしておいて欲しい条件を記述して、 運用時は重たいから削除するような時にも使えそうですね...

開発時には assert で引っかからなかったからって、 運用時に引っかからないわけではないから注意しないといけないよという記述が、 書籍 達人プログラマ にありました。

プログラムの配備時に 表明 をオフにすることは、綱渡りの練習を一度うまくやり終えただけで 本番の綱渡りに挑戦するようなものです。ドラマチックな価値はありますが、 生命保険はおりないはずです。
達人プログラマ - 第4章妄想の達人 - 23 表明プログラミング

表明(ひょうめい、assertion)とは、プログラミングにおける概念のひとつであり、 そのプログラムの前提条件を示すのに使われる。アサーションとも呼ばれる。
表明 - Wikipedia

# 5. なんで assert は文なの?

例えば最初の例なら、if 文と raise 文で、全く同じことができます。 なぜ、assert は関数ではなく文なのでしょうか?

def assertion(condition):
    if __debug__:
        if not condition:
            raise AssertionError

if assertion(1 + 1 == 3):
    raise AssertionError
>>> def assertion(condition):
...     if not condition:
...         raise AssertionError
... 
>>> if assertion(1 + 1 == 3):
...     raise AssertionError
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in assertion
AssertionError
>>> 

# 答え: 最適化のため

実行時に最適化オプション -O を指定することで assert 文は実行されなくなります。 もし関数だった場合、引数が評価されてから、関数が実行され、それから実行するかどうかの判定がなされます。 これは通常の関数では実装することができません。

def assertion(condition):
    if __debug__:
        if not condition:
            raise AssertionError

def f(x):
    print(x)
    return False

assertion(f('Hello, world!'))
assert f('Nihao, shijie!')
$ # 最適化オプション有効
$ python -O
>>>
>>> ...  # 中略
>>>
>>> assertion(f('Hello, world!'))
Hello, world!  <-------- 関数だと print(x) が実行されてしまう 
>>> assert f('Nihao, shijie!')
>>>

Python の設計: なぜ assert は文で関数ではないのですか? - Stackoverflow
design of python: why is assert a statement and not a function? - Stackoverflow

assert を関数としてではなく、文としておくこと(そして予約語とすることに)に何か利点があるのですか?
"Are there any advantages to having assert be a statement (and reserved word) instead of a function?"

1. @mgilson が指摘するように、ユーザ定義関数に上書きされないことで assert 文に書かれた式をコンパイル時に確実に無効にできる。
1. Cannot be reassigned to a user function, meaning it can be effectively disabled at compile time as @mgilson pointed out.

2. 2 番目のオプションのパラメータの評価は、アサーションが失敗した場合または失敗するまで延期されます。 関数や関数の引数を使ってそれを行うのは厄介です(ラムダを渡す必要があるでしょう)。 2 番目のパラメータの評価を延期しないと、追加のオーバーヘッドが発生します。
2. The evaluation of the second, optional parameter is deferred until if/when the assertion fails. Awkward to do that with functions and function arguments (would need to pass a lambda.) Not deferring the evaluation of the second parameter would introduce additional overhead.

単純な形式 assert expression は

if __debug__:
    if not expression: raise AssertionError

と等価です。

7.3. assert 文 - Python 言語リファレンス

# 6. その他の「文」と「関数」の例

# 6.1. print 文から print 関数に

反対に、Python 2 の頃は print 文がありましたが廃止されて関数になりました。これは print が文である必要がないからです。

以下の print 関数についての議論はメーリングリスト python-3000 に記載された Guido 自身のメッセージを抽出したものです[2]:
The following arguments for a print() function are distilled from a python-3000 message by Guido himself [2]:

print は、たった1つの専用の文を持つアプリケーションレベルの機能です。 Python の世界では、文法で対応することはコンパイラの助けなしに解決できなかった時に使う最後の手段です。 print はそのような例外的な状況ではない。
print is the only application-level functionality that has a statement dedicated to it. Within Python's world, syntax is generally used as a last resort, when something can't be done without help from the compiler. Print doesn't qualify for such an exception.

PEP 3105 - Make print a function

# 6.2. del 文

del は、なぜ関数ではなく文なのでしょうか?

var = 0
del var

それは  del が変数の中のオブジェクトではなく変数そのものを削除するから  です。 以下の記事でご説明させていただきました。代入から初めて、代入の上位概念である「束縛」へと理解を持ち上げていきます。



# おわりに

ここまで以下のように見てきました。

以上になります。ありがとうございました。

Last Updated: 12/13/2019, 10:12:44 PM