Table Of Contents

Previous topic

基本的なテストの設定

Next topic

テスト関数 (funcargs) にオブジェクトを注入

テストのアサーションにおける書き込みとレポート

assert 文によるアサーション

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 といったテストに失敗したときに分かりやすい内容を表示します。

コンテキストに依存した内容の比較

New in version 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 =========================

複数のケースにおいて、特別な比較が行われます:

  • 長い文字列の比較: コンテキスト diff を表示
  • 長いシーケンスの比較: 最初に失敗したインデックス
  • ディクショナリの比較: 異なるエントリ

より多くのサンプルについては レポートのデモ 参照してください。

アサーション比較の定義

pytest_assertrepr_compare フックを実装することで独自の詳細説明を追加できます。

_pytest.hookspec.pytest_assertrepr_compare(config, op, left, right)[source]

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

高度なアサートイントロスペクション

New in version 2.1.

失敗するアサーションに関する詳細のレポートは、実行前に assert 文を書き換えるか、または assert 式を再評価して中間値を記録するかのどちらかの方法で行われます。どちらの方法を使うかは assert の位置、pytest の設定、pytest を実行するのに使われる Python バージョンに依存します。 assert expr, message のように直接コード内でメッセージを記述した assert 文は、アサートイントロスペクションが行われず、指定したメッセージがトレースバックに表示されることに注意してください。

デフォルトでは、Python バージョンが 2.6 以上の場合、py.test はテストモジュールの assert 文を書き換えます。書き換えられた assert 文は、イントロスペクション情報をアサーションの失敗メッセージに追加します。py.test は、テストコレクション処理で検出したテストモジュールのみを直接書き換えます。そのため、テストモジュールではないサポートライブラリの assert 文は書き換えられません。

Note

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 を参照してください。

New in version 2.1: 代替イントロスペクション手法として assert 書き換え機能を追加

Changed in version 2.1: --assert オプションを追加。 --no-assert--nomagic を廃止。