# assert 文でテストする。





assert 文は簡単にテストをする方法を提供してくれます。




書式は、次の通りで、「条件」が False になると AssertionError が発生します。

assert 条件, エラー文

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

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


# 1. 使用例

# ◯ 簡単なもの

1 + 1 == 2 なので 1 + 1 == 3 など間違ったことを書くと エラーになってしまいます。

assert 1 + 1 == 3, '残念!'
>>> assert 1 + 1 == 3, '残念!'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: 残念!
>>> 

# ◯ 複雑なもの

例えば sort をテストするコードを書いてみます。 test 関数は、0 ~ 99 のランダムな 100 個の整数を 正しくソートできるかを 100 回確認させています。

いいサンプルとかというわけではなく、 これと言ったものが思いつけなかったので、強引に使ったものをご提示いたしました。

# 2. 動作例

# 最適化オプション -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__" ってなんだろう... 訳せませんでした...

# 定数 __debug__

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

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

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

# 3. 2つのツール

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

# 1. pytest

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



# 2. unittest

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

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

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

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

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

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

# 3. 調査

国外では 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.

上記の記事で面白かったのは Django よりも Flask の方がすこしだけ、 人気が上だったこと。 誰に対する調査なのか、あまりちゃんと見れてないのですが...

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

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

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

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

assert 1 + 1 == 2

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

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

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

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

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

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

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

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

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

# 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. なんで最適化(無効化)するの?

# 答え(その1): 本番環境で例外を投げるために assert 文を乱用させないため

assert 文は最適化オプションをつけると評価されません。 ということは、assert 文は、テスト専用の文なのかなと思いました。 本番環境では使わないということかなと思っています。

ここで疑問なのは テストのコード と本番で 運用する際のコード がごっちゃになることってあるのかなという疑問があります。 混ざることがないのだから専用の文ではなくて関数でよかったんじゃないかなとも思いましたが。

結局は本番環境で例外を投げるために assert 文を乱用させないためかなと思いました。 以下の記事は Go 言語に関する記事ですが、参考になります。

アサート(assert)がない理由は?

Go言語では、アサートを提供していません。アサートが便利である点は疑う余地はありませんが、 我々の経験的には、プログラマはアサートを、適切なエラーハンドリングとエラーレポートを考慮せずに済ますためのツールとして使っています。 適切なエラーハンドリングとは、致命的ではないエラーが起きたときにクラッシュさせずに、処理を継続させることです。

また、適切なエラーレポートとは、そのレポートされたエラーが直接的かつ適切な内容で、 プログラマが巨大なクラッシュトレースに対して行う調査を手助けします。 正確なエラー情報は、エラーを調べているプログラマがコードを熟知していないときは、特に重要です。

# 答え(その2): 運用時は重たい処理を外すため

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

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

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

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

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

# 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

# 2. del 文

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

var = 0
del var

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