目次

前のトピックへ

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

次のトピックへ

拡張された xUnit スタイルのセットアップフィクスチャ

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

関数の引数を使った依存性の注入

py.test は、テスト関数にオブジェクトを注入し、テストの実行に関連付けてそのライフサイクルを細かく制御できます。さらに別のオブジェクトでテスト関数を複数回実行することもできます。

オブジェクトを注入するための基本的な仕組みは funcarg 機構 とも呼ばれます。ある引数に対して、その引数を受け取るテスト関数が呼ばれることで最終的にオブジェクトが注入されるからです。古典的な xUnit のやり方とは異なり funcargs依存性の注入 に密接に関連したものです。その根拠は、テストコードを実行するために必要なオブジェクトからテストコードを分離するのに役立つからです。

テスト関数へ渡される値を作成するために、テスト関数のコンテキストに対してフルアクセスをもったファクトリー関数が呼ばれます。そして、ファイナライザーを登録したり、ライフサイクルキャッシュヘルパーを実行します。ファクトリー関数は、同じテストクラスかテストモジュール、ディレクトリ毎の conftest.py ファイル、外部プラグインであろうと、そのいずれでも実装できます。これにより、テストの実行に必要なテストコードとオブジェクトを完全に分離できます。

テスト関数は、 パラメーターテスト で説明したようなケースなら複数回呼び出すこともあります。これは、例えば、別々のデータベースのバックエンド、または複数の数値の引数セットをテストしたいときや、テスト関数の同じセットを再利用したいといったときにとても便利です。

py.test には 組み込み関数の引数 が付属していて、そのサンプルを紹介する節に洗練された利用方法があります。

基本的な注入の例

簡単な自己完結型のテストモジュールを見てみましょう:

# ./test_simplefactory.py の内容
def pytest_funcarg__myfuncarg(request):
    return 42

def test_function(myfuncarg):
    assert myfuncarg == 17

このテスト関数は myfuncarg という名前のオブジェクトへの注入を必要とします。この場合 py.test は、同じモジュール内の pytest_funcarg__myfuncarg というファクトリー関数を見つけて呼び出します。

次のようにテストが実行されます:

$ py.test test_simplefactory.py
=========================== test session starts ============================
platform linux2 -- Python 2.7.1 -- pytest-2.2.4
collecting ... collected 1 items

test_simplefactory.py F

================================= FAILURES =================================
______________________________ test_function _______________________________

myfuncarg = 42

    def test_function(myfuncarg):
>       assert myfuncarg == 17
E       assert 42 == 17

test_simplefactory.py:5: AssertionError
========================= 1 failed in 0.01 seconds =========================

これは実際にテスト関数が myfuncarg 引数の値が 42 で呼び出されて、そのアサートに失敗します。py.test がどういった手順でテスト関数を呼び出すかを説明します:

  1. py.test は test_ という接頭辞をもつ test_function探索します 。テスト関数は myfuncarg という関数の引数を必要とします。 pytest_funcarg__myfuncarg という名前を調べて一致するファクトリー関数が検出されます。
  1. pytest_funcarg__myfuncarg(request) が呼び出されて myfuncarg の値を返します。
  1. そのテスト関数は test_function(42) として呼び出されます。この実行結果は、アサーションが不一致なので上述した例外を発生させます。

関数の引数に誤字があったり、利用できないものを使おうとしたら、利用できる関数の引数の一覧と共にエラーが表示されるので注意してください。

いつでも次のようにして:

py.test --fixtures test_simplefactory.py

利用できる関数の引数 (“リソース” とも見なせる) を調べられます。

funcarg request オブジェクト

funcarg ファクトリー関数は、特別なテスト関数呼び出しに関連付けられた request オブジェクトを受け取ります。request オブジェクトは funcarg ファクトリーへ渡されて、テスト設定とコンテキストへのアクセスを提供します:

class _pytest.python.FixtureRequest[ソース]

A request for a fixture from a test or fixture function.

A request object gives access to the requesting test context and has an optional param attribute in case the fixture is parametrized indirectly.

cls[ソース]

class (can be None) where the test function was collected.

config[ソース]

the pytest config object associated with this request.

function[ソース]

test function object if the request has a per-function scope.

keywords[ソース]

keywords/markers dictionary for the underlying node.

module[ソース]

python module object where the test function was collected.

FixtureRequest.addfinalizer(finalizer)[ソース]

add finalizer/teardown function to be called after the last test within the requesting test context finished execution.

FixtureRequest.cached_setup(setup, teardown=None, scope='module', extrakey=None)[ソース]

(deprecated) Return a testing resource managed by setup & teardown calls. scope and extrakey determine when the teardown function will be called so that subsequent calls to setup would recreate the resource. With pytest-2.3 you often do not need cached_setup() as you can directly declare a scope on a fixture function and register a finalizer through request.addfinalizer().

パラメタ:
  • teardown – function receiving a previously setup resource.
  • setup – a no-argument function creating a resource.
  • scope – a string value out of function, class, module or session indicating the caching lifecycle of the resource.
  • extrakey – added to internal caching key of (funcargname, scope).
FixtureRequest.applymarker(marker)[ソース]

Apply a marker to a single test function invocation. This method is useful if you don’t want to have a keyword/marker on all function invocations.

パラメタ:marker – a _pytest.mark.MarkDecorator object created by a call to py.test.mark.NAME(...).
FixtureRequest.getfuncargvalue(argname)[ソース]

Dynamically retrieve a named fixture function argument.

As of pytest-2.3, it is easier and usually better to access other fixture values by stating it as an input argument in the fixture function. If you only can decide about using another fixture at test setup time, you may use this function to retrieve it inside a fixture function body.

パラメーター化したテスト関数の複数呼び出し

別の関数の引数の値を取って呼び出す新たなテスト関数を追加することで、同じテスト関数に対して複数回の実行をパラメーター化して実行できます。簡単な自己完結型のサンプルコードを見てみましょう:

テストを生成する基本的な例

同じテスト関数に対する複数回呼び出しに生成する pytest_generate_tests フックを使うテストモジュールを見てみましょう:

# test_example.py の内容
def pytest_generate_tests(metafunc):
    if "numiter" in metafunc.fixturenames:
        metafunc.parametrize("numiter", range(10))

def test_func(numiter):
    assert numiter < 9

このサンプルコードを実行すると range(10) のリストの要素を1つずつ引数に渡す test_func を10回実行します:

$ py.test test_example.py
=========================== test session starts ============================
platform linux2 -- Python 2.7.1 -- pytest-2.2.4
collecting ... collected 10 items

test_example.py .........F

================================= FAILURES =================================
_______________________________ test_func[9] _______________________________

numiter = 9

    def test_func(numiter):
>       assert numiter < 9
E       assert 9 < 9

test_example.py:6: AssertionError
==================== 1 failed, 9 passed in 0.02 seconds ====================

分かりやすいように numiter の値が 9 のときのみテストが失敗します。 pytest_generate_tests(metafunc) フックは、実際にテストを実行するときとは違うフェーズの、テストコレクションで呼ばれることに注意してください。では、テストコレクションがどうなるかをちょっと見てみましょう:

$ py.test --collectonly test_example.py
=========================== test session starts ============================
platform linux2 -- Python 2.7.1 -- pytest-2.2.4
collecting ... collected 10 items
<Module 'test_example.py'>
  <Function 'test_func[0]'>
  <Function 'test_func[1]'>
  <Function 'test_func[2]'>
  <Function 'test_func[3]'>
  <Function 'test_func[4]'>
  <Function 'test_func[5]'>
  <Function 'test_func[6]'>
  <Function 'test_func[7]'>
  <Function 'test_func[8]'>
  <Function 'test_func[9]'>

=============================  in 0.00 seconds =============================

テスト実行時に 7 の値が渡されるときだけ実行したい場合は次のようにして行います:

$ py.test -v -k 7 test_example.py  # または -k test_func[7]
=========================== test session starts ============================
platform linux2 -- Python 2.7.1 -- pytest-2.2.4 -- /home/hpk/venv/0/bin/python
collecting ... collected 10 items

test_example.py:5: test_func[7] PASSED

======================= 9 tests deselected by '-k7' ========================
================== 1 passed, 9 deselected in 0.01 seconds ==================

さらにパラメーターテストのサンプル を見たくなりますね。

metafunc オブジェクト

metafunc オブジェクトは pytest_generate_tests フックへ渡されます。これはテスト関数を検査したり、テスト設定またはテスト関数が定義されているクラスやモジュールで指定された値を取るテストを生成するのに役立ちます:

metafunc.fixturenames: テスト関数へ渡される引数セット

metafunc.function: 対象となる Python のテスト関数

metafunc.cls: テスト関数が定義されているところのクラスオブジェクト、または None

metafunc.module: テスト関数が定義されているところのモジュールオブジェクト

metafunc.config: コマンドラインオプションと汎用的な設定オブジェクト

Metafunc.parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)[ソース]

Add new invocations to the underlying test function using the list of argvalues for the given argnames. Parametrization is performed during the collection phase. If you need to setup expensive resources see about setting indirect=True to do it rather at test setup time.

パラメタ:
  • argnames – an argument name or a list of argument names
  • argvalues – a list of values for the argname or a list of tuples of values for the list of argument names.
  • indirect – if True each argvalue corresponding to an argname will be passed as request.param to its respective argname fixture function so that it can perform more expensive setups during the setup phase of a test rather than at collection time.
  • ids – list of string ids each corresponding to the argvalues so that they are part of the test id. If no ids are provided they will be generated automatically from the argvalues.
  • scope – if specified it denotes the scope of the parameters. The scope is used for grouping tests by parameter instances. It will also override any fixture-function defined scope, allowing to set a dynamic scope using test context or configuration.
Metafunc.addcall(funcargs=None, id=_notexists, param=_notexists)[ソース]

(deprecated, use parametrize) Add a new call to the underlying test function during the collection phase of a test run. Note that request.addcall() is called during the test collection phase prior and independently to actual test execution. You should only use addcall() if you need to specify multiple arguments of a test function.

パラメタ:
  • funcargs – argument keyword dictionary used when invoking the test function.
  • id – used for reporting and identification purposes. If you don’t supply an id an automatic unique id will be generated.
  • param – a parameter which will be exposed to a later fixture function invocation through the request.param attribute.