py.test は、テストで期待値と実際の値を検証するのに Python 標準の assert 文が使えます。例えば、次のようにテストを作成します:
# test_assert1.py の内容
def f():
return 3
def test_function():
assert f() == 4
このサンプルは、関数が特定の値を返すのをアサートします。このアサーションが失敗した場合、関数呼び出しの返り値が表示されます:
$ py.test test_assert1.py
=========================== test session starts ============================
platform linux2 -- Python 2.7.1 -- pytest-2.2.4
collecting ... collected 1 items
test_assert1.py F
================================= FAILURES =================================
______________________________ test_function _______________________________
def test_function():
> assert f() == 4
E assert 3 == 4
E + where 3 = f()
test_assert1.py:5: AssertionError
========================= 1 failed in 0.01 seconds =========================
py.test は、関数呼び出し、属性、比較、バイナリや単項演算子といった処理を含む通常の部分式の値を表示する機能があります (py.test によるテスト失敗時のレポートのデモ を参照) 。この機能により、定型的なコードを必要とせず、Python イディオム的な概念も利用できます。その上でイントロスペクション情報を失うこともありません。
但し、次のようにアサーションと一緒にメッセージを指定した場合:
assert a % 2 == 0, "value was odd, should be even"
そこでアサートイントロスペクションを行わず、このメッセージは単純にトレースバックで表示されます。
アサートイントロスペクションの詳細については 高度なアサートイントロスペクション を参照してください。
発生した例外のアサーションを行うには、次のようにコンテキスト マネージャーとして pytest.raises を使います:
import pytest
with pytest.raises(ZeroDivisionError):
1 / 0
もし実際の例外の情報を調べる必要があるなら、次のように行います:
with pytest.raises(RuntimeError) as excinfo:
def f():
f()
f()
# excinfo.type, excinfo.value, excinfo.traceback といった関連する値を確認する
Python 2.4 でも同じように動作するテストコードを書きたいなら、例外発生を期待するテストを行う別の方法が2つあります:
pytest.raises(ExpectedException, func, *args, **kwargs)
pytest.raises(ExpectedException, "func(*args, **kwargs)")
両方とも指定した関数へ args と kwargs を渡して実行し、引数として与えた ExpectedException が発生することをアサートします。このレポートは no exception または wrong exception といったテストに失敗したときに分かりやすい内容を表示します。
バージョン 2.0 で追加.
py.test は、比較するときにコンテキスト依存の情報を分かりやすく表示します。例えば、:
# test_assert2.py の内容
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
assert set1 == set2
このモジュールを実行すると:
$ py.test test_assert2.py
=========================== test session starts ============================
platform linux2 -- Python 2.7.1 -- pytest-2.2.4
collecting ... collected 1 items
test_assert2.py F
================================= FAILURES =================================
___________________________ test_set_comparison ____________________________
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
> assert set1 == set2
E assert set(['0', '1', '3', '8']) == set(['0', '3', '5', '8'])
E Extra items in the left set:
E '1'
E Extra items in the right set:
E '5'
test_assert2.py:5: AssertionError
========================= 1 failed in 0.01 seconds =========================
複数のケースにおいて、特別な比較が行われます:
より多くのサンプルについては レポートのデモ 参照してください。
pytest_assertrepr_compare フックを実装することで独自の詳細説明を追加できます。
return explanation for comparisons in failing assert expressions.
Return None for no custom explanation, otherwise return a list of strings. The strings will be joined by newlines but any newlines in a string will be escaped. Note that all but the first line will be indented sligthly, the intention is for the first line to be a summary.
例として、conftest.py に次のフックを追加してみます。これは Foo オブジェクトの別の説明を提供します:
# conftest.py の内容
from test_foocompare import Foo
def pytest_assertrepr_compare(op, left, right):
if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
return ['Comparing Foo instances:',
' vals: %s != %s' % (left.val, right.val)]
ここで次のテストモジュールがあります:
# test_foocompare.py の内容
class Foo:
def __init__(self, val):
self.val = val
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
assert f1 == f2
このテストモジュールを実行すると、conftest ファイルで定義した独自の出力内容が表示されます:
$ py.test -q test_foocompare.py
collecting ... collected 1 items
F
================================= FAILURES =================================
_______________________________ test_compare _______________________________
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
> assert f1 == f2
E assert Comparing Foo instances:
E vals: 1 != 2
test_foocompare.py:8: AssertionError
1 failed in 0.01 seconds
バージョン 2.1 で追加.
失敗するアサーションに関する詳細のレポートは、実行前に assert 文を書き換えるか、または assert 式を再評価して中間値を記録するかのどちらかの方法で行われます。どちらの方法を使うかは assert の位置、pytest の設定、pytest を実行するのに使われる Python バージョンに依存します。 assert expr, message のように直接コード内でメッセージを記述した assert 文は、アサートイントロスペクションが行われず、指定したメッセージがトレースバックに表示されることに注意してください。
デフォルトでは、Python バージョンが 2.6 以上の場合、py.test はテストモジュールの assert 文を書き換えます。書き換えられた assert 文は、イントロスペクション情報をアサーションの失敗メッセージに追加します。py.test は、テストコレクション処理で検出したテストモジュールのみを直接書き換えます。そのため、テストモジュールではないサポートライブラリの assert 文は書き換えられません。
ノート
py.test は、インポート時にテストモジュールを書き換えます。新たに pyc ファイルを書き込むためにインポートフックを使うことでこの処理を行います。この処理はほとんど透過的に行われます。但し、自分でインポートを行ってごちゃごちゃになっている場合、そのインポートフックがインターフェースになる可能性があります。このようなケースでは、単純に --assert=reinterp か --assert=plain を使ってください。さらに、新たに pyc ファイルを書き込めない場合、書き換えはサイレントモードで失敗します。例えば、読み込み専用ファイルシステムや zip ファイルで行うようなときです。
assert 文が書き換えられない、または Python バージョン 2.6 よりも小さい場合、py.test はアサーションの再解釈を行います。アサーションの再解釈では、py.test が、assert 文の失敗する部分式を見つけるために assert 文を含む関数のフレームを辿ります。py.test にアサーションの再解釈を行うよう強制するには --assert=reinterp オプションを指定します。
アサーションの再解釈は、assert 文の書き換えを行わないことの注意が必要です: それは assert 式の評価が副作用をもつ場合、中間値が安全に決定しないという警告を受け取るかもしれません。この問題の一般的な例として、ファイルを読み込むアサーションがあります:
assert f.read() != '...'
このアサーションが失敗した場合、その再評価はおそらく成功します!つまり再評価において2回目に呼び出されたときに f.read() が空の文字列を返すからです。とはいえ、このアサーションを書き換えて、そういったトラブルを避けるのは簡単です:
content = f.read()
assert content != '...'
全てのアサートイントロスペクションを無効にするには --assert=plain を指定します。
詳細については、Benjamin Peterson が詳しくまとめた Behind the scenes of py.test’s new assertion rewriting を参照してください。
バージョン 2.1 で追加: 代替イントロスペクション手法として assert 書き換え機能を追加
バージョン 2.1 で変更: --assert オプションを追加。 --no-assert と --nomagic を廃止。