pytestを䜿甚したPythonテスト。 ビルトむンフィクスチャ、第4ç« 

戻る 次ぞ


pytestに付属しおいる組み蟌みのフィクスチャを䜿甚するず、テストで非垞に䟿利なこずを簡単か぀自然に行うこずができたす。 たずえば、䞀時ファむルの凊理に加えお、pytestには、コマンドラむンパラメヌタヌぞのアクセス、テストセッション間の通信、出力ストリヌムの確認、環境倉数の倉曎、アラヌトのポヌリングのための組み蟌みフィクスチャが含たれおいたす。



Tasksプロゞェクトの゜ヌスコヌドず、この本に瀺されおいるすべおのテストの゜ヌスコヌドは、 pragprog.comにある本のWebペヌゞのリンクから入手できたす。 テストコヌドを理解するために゜ヌスコヌドをダりンロヌドする必芁はありたせん。 テストコヌドは、䟋では䟿利な圢匏で瀺されおいたす。 ただし、プロゞェクトのタスクを実行したり、テストサンプルを調敎しお自分のプロゞェクトをチェックしたりするには手を瞛っおいない、曞籍のWebペヌゞにアクセスしお䜜品をダりンロヌドする必芁がありたす。 曞籍のりェブペヌゞには、 正誀衚の投皿ずディスカッションフォヌラムぞのリンクがありたす。

ネタバレの䞋には、このシリヌズの蚘事のリストがありたす。



前の章では、フィクスチャずは䜕か、フィクスチャの曞き方、テストデヌタずセットアップおよび分解コヌドのためにそれらを䜿甚する方法に぀いお芋おきたした。


たた、conftest.pyを䜿甚しお、耇数のテストファむルのテスト間でフィクスチャを共有したした。 第3章の終わりに、Tasksプロゞェクトの49ペヌゞのpytest Fixturesに次のフィクスチャがむンストヌルされたした。それらが必芁です。


通垞のフィクスチャを再利甚するこずは、pytestの開発者が䞀般的に必芁ずするフィクスチャをpytestに含めたずいう良いアむデアです。 59ペヌゞのTasksプロゞェクトのフィクスチャのスコヌプ倉曎セクションで、Tasksプロゞェクトがtmpdirずtmpdir_factoryをどのように䜿甚するかを既に芋おきたした。これらに぀いおは、この章で詳しく説明したす。


pytestに付属しおいる組み蟌みのフィクスチャは、テストで非垞に䟿利なこずを簡単か぀自然に行うのに圹立ちたす。 たずえば、䞀時ファむルの凊理に加えお、pytestには、コマンドラむンパラメヌタヌぞのアクセス、テストセッション間の通信、出力ストリヌムのチェック、環境倉数の倉曎、およびアラヌトのポヌリングのための組み蟌みフィクスチャが含たれおいたす。 組み蟌みのフィクスチャは、pytestコア機胜の拡匵機胜です。 次に、最も䞀般的に䜿甚されるいく぀かのむンラむンフィクスチャを順番に芋おみたしょう。


tmpdirおよびtmpdir_factoryの䜿甚


ファむルの読み取り、曞き蟌み、たたは倉曎を行うものをテストする堎合は、tmpdirを䜿甚しお単䞀のテストで䜿甚されるファむルたたはディレクトリを䜜成できたす。たた、耇数のテスト甚にディレクトリを蚭定する堎合はtmpdir_factoryを䜿甚できたす。


tmpdir tmpdirには関数スコヌプがあり、 tmpdir_factoryフィクスチャにはセッションスコヌプがありたす。 1぀のテストだけに䞀時ディレクトリたたはファむルを必芁ずする単䞀のテストでは、 tmpdirを䜿甚できたす。 これはフィクスチャにも圓おはたりたす。フィクスチャは、テスト機胜ごずに再䜜成する必芁があるディレクトリたたはファむルをカスタマむズしたす。


tmpdirを䜿甚した簡単な䟋を次に瀺しtmpdir 。


ch4/test_tmpdir.py

 def test_tmpdir(tmpdir): # tmpdir    ,    # join()  ,    , #     a_file = tmpdir.join('something.txt') #    a_sub_dir = tmpdir.mkdir('anything') #      (  ) another_file = a_sub_dir.join('something_else.txt') #    'something.txt' a_file.write('contents may settle during shipping') #    'anything/something_else.txt' another_file.write('something different') #      assert a_file.read() == 'contents may settle during shipping' assert another_file.read() == 'something different' 

tmpdirから返される倀は、タむプpy.path.local.1オブゞェクトです。これは、䞀時ディレクトリずファむルに必芁なもののすべおです。 ただし、1぀のトリックがありたす。 tmpdirフィクスチャtmpdir 関数スコヌプずしお定矩されおいるため、 tmpdir䜿甚しお、耇数のテスト関数で䜿甚できるフォルダヌたたはファむルを䜜成するこずはできたせん。 関数クラス、モゞュヌル、セッション以倖のスコヌプを持぀フィクスチャの堎合、 tmpdir_factoryが利甚可胜です。


tmpdir_factory tmpdir_factory tmpdirに非垞に䌌おいたすが、異なるむンタヌフェヌスを持っおいたす。 「スコヌプフィクスチャの仕様」セクション56ペヌゞで説明されおいるように、関数゚リアフィクスチャは各テスト関数に察しお1回実行され、モゞュヌル゚リアフィクスチャはモゞュヌルごずに1回実行され、クラスフィクスチャはクラスごずに1回実行され、゚リア怜蚌テストセッションごずに1回動䜜したす。 したがっお、セッション領域レコヌドで䜜成されたリ゜ヌスには、セッション党䜓の有効期間がありたす。 tmpdirずtmpdir_factory類䌌性を瀺すために、 tmpdir_factory tmpdir䟋をtmpdir 。


ch4 / test_tmpdir.py

 def test_tmpdir_factory(tmpdir_factory): #      . a_dir   # ,    tmpdir a_dir = tmpdir_factory.mktemp('mydir') # base_temp    'mydir'    #  getbasetemp(),  # ,    base_temp = tmpdir_factory.getbasetemp() print('base:', base_temp) #       , #    ' test_tmpdir ()',   , #    a_dir  tmpdir a_file = a_dir.join('something.txt') a_sub_dir = a_dir.mkdir('anything') another_file = a_sub_dir.join('something_else.txt') a_file.write('contents may settle during shipping') another_file.write('something different') assert a_file.read() == 'contents may settle during shipping' assert another_file.read() == 'something different' 

最初の行はmktemp('mydir')を䜿甚しおディレクトリを䜜成し、 a_dirたす。 関数の残りの郚分では、 tmpdirから返されるtmpdirず同じ方法でa_dirを䜿甚できたす。


tmpdir_factory䟋の2行目では、 getbasetemp()関数はこのセッションに䜿甚されるベヌスディレクトリを返したす。 この䟋のprintステヌトメントは、システム䞊のディレクトリを衚瀺できるようにするために必芁です。 それがどこにあるか芋おみたしょう


 $ cd /path/to/code/ch4 $ pytest -q -s test_tmpdir.py::test_tmpdir_factory base: /private/var/folders/53/zv4j_zc506x2xq25l31qxvxm0000gn/T/pytest-of-okken/pytest-732 . 1 passed in 0.04 seconds 

このベヌスディレクトリはシステムずナヌザヌに䟝存し、 pytest - NUMが増加するたびにpytest - NUMがセッションごずに倉化したす。 ベヌスディレクトリは、セッション埌にそのたた残されたす。 pytestはそれをクリヌンアップし、最埌の数個の䞀時ベヌスディレクトリのみがシステムに残りたす。これは、テスト実行埌にファむルをチェックするのが埅ち遠しい堎合は問題ありたせん。


pytest --basetemp=mydirを䜿甚する必芁がある堎合は、独自のベヌスディレクトリを指定するこずもできたす。


他の領域に䞀時ディレクトリを䜿甚する


tmpdir_factoryから䞀時ディレクトリずセッション領域ファむルを取埗し、 tmpdirから関数ディレクトリず領域ファむルを取埗したす。 しかし、他の領域はどうですか モゞュヌルたたはクラスのスコヌプの䞀時ディレクトリが必芁な堎合はどうなりたすか これを行うには、目的のサむズの領域の別のフィクスチャを䜜成したす。これにはtmpdir_factoryを䜿甚する必芁がありたす。


たずえば、テストでいっぱいのモゞュヌルがあり、それらの倚くがjsonファむルからいく぀かのデヌタを読み取るこずができるず仮定したす。 フィクスチャボリュヌムフィクスチャをモゞュヌル自䜓たたはconftest.pyファむルに配眮するこずができたした。これにより、デヌタファむルが次のように蚭定されたす。


ch4 / authors / conftest.py

 """Demonstrate tmpdir_factory.""" import json import pytest @pytest.fixture(scope='module') def author_file_json(tmpdir_factory): """     .""" python_author_data = { 'Ned': {'City': 'Boston'}, 'Brian': {'City': 'Portland'}, 'Luciano': {'City': 'Sau Paulo'} } file = tmpdir_factory.mktemp('data').join('author_file.json') print('file:{}'.format(str(file))) with file.open('w') as f: json.dump(python_author_data, f) return file 

フィクスチャauthor_file_json()は、 dataずいう䞀時ディレクトリを䜜成し、デヌタディレクトリにauthor_file.jsonずいうファむルを䜜成したす。 次に、 python_author_data蟞曞をjsonずしお曞き蟌みたす。 これはフィクスチャ゚リアモゞュヌルであるため、テストを䜿甚しおモゞュヌルごずにjsonファむルが1回だけ䜜成されたす。


ch4 / authors / test_authors.py

 """ ,    .""" import json def test_brian_in_portland(author_file_json): """,   .""" with author_file_json.open() as f: authors = json.load(f) assert authors['Brian']['City'] == 'Portland' def test_all_have_cities(author_file_json): """        .""" with author_file_json.open() as f: authors = json.load(f) for a in authors: assert len(authors[a]['City']) > 0 

䞡方のテストで同じJSONファむルが䜿甚されたす。 1぀のテストデヌタファむルが耇数のテストで機胜する堎合、䞡方のテストで再䜜成しおも意味がありたせん。


pytestconfigを䜿甚する


pytestconfigビルトむンフィクスチャを䜿甚するず、pytestが匕数ずコマンドラむンオプション、構成ファむル、プラグむン、およびpytestを起動したディレクトリでどのように動䜜するかを制埡できたす。 pytestconfigフィクスチャはrequest.configのショヌトカットであり、pytestのドキュメントでは「 pytest configオブゞェクト 」pytest構成オブゞェクトず呌ばれるこずもありたす。


pytestconfigの仕組みを調べるには、カスタムコマンドラむンパラメヌタヌを远加し、テストからパラメヌタヌ倀を読み取る方法を確認できたす。 pytestconfigからコマンドラむンパラメヌタヌの倀を盎接読み取るこずができたすが、パラメヌタヌを远加しお分析するには、フック関数を远加する必芁がありたす。 95ペヌゞの第5章「プラグむン」で詳しく説明するフック関数は、pytestの動䜜を制埡する別の方法であり、プラグむンでよく䜿甚されたす。 ただし、カスタムコマンドラむンオプションを远加しおpytestconfigから読み取るこずは非垞に䞀般的であるため、ここで説明したす。


pytestフック pytest_addoptionを䜿甚しお、pytestコマンドラむンで既に利甚可胜なパラメヌタヌにいく぀かのパラメヌタヌを远加したす。


ch4 / pytestconfig / conftest.py

 def pytest_addoption(parser): parser.addoption("--myopt", action="store_true", help="some boolean option") parser.addoption("--foo", action="store", default="bar", help="foo: bar or baz") 

pytest_addoptionを䜿甚しおコマンドラむンパラメヌタヌを远加するには、プラグむンを䜿甚するか、プロゞェクトディレクトリ構造の最䞊郚にあるconftest.pyファむルを䜿甚したす。 testサブディレクトリでこれを行うべきではありたせん。


--myoptおよび--foo <value>オプションが前のコヌドに远加され、ヘルプ行が次のように倉曎されたした。


 $ cd /path/to/code/ch4/pytestconfig $ pytest --help usage: pytest [options] [file_or_dir] [file_or_dir] [...] ... custom options: --myopt some boolean option --foo=FOO foo: bar or baz ... 

これで、テストからこれらのオプションにアクセスできたす。


ch4 / pytestconfig / test_config.py

 import pytest def test_option(pytestconfig): print('"foo" set to:', pytestconfig.getoption('foo')) print('"myopt" set to:', pytestconfig.getoption('myopt')) 

仕組みを芋おみたしょう。


 $ pytest -s -q test_config.py::test_option "foo" set to: bar "myopt" set to: False .1 passed in 0.01 seconds $ pytest -s -q --myopt test_config.py::test_option "foo" set to: bar "myopt" set to: True .1 passed in 0.01 seconds $ pytest -s -q --myopt --foo baz test_config.py::test_option "foo" set to: baz "myopt" set to: True .1 passed in 0.01 seconds 

pytestconfigはフィクスチャであるため、他のフィクスチャからも取埗できたす。 必芁に応じお、オプション名のフィクスチャを䜜成できたす。䟋


ch4 / pytestconfig / test_config.py

 @pytest.fixture() def foo(pytestconfig): return pytestconfig.option.foo @pytest.fixture() def myopt(pytestconfig): return pytestconfig.option.myopt def test_fixtures_for_options(foo, myopt): print('"foo" set to:', foo) print('"myopt" set to:', myopt) 

たた、远加されたパラメヌタヌだけでなく、組み蟌みパラメヌタヌにアクセスしたり、pytestの起動方法に関する情報ディレクトリ、匕数などにアクセスしたりするこずもできたす。


次に、いく぀かの倀ず構成オプションの䟋を瀺したす。


 def test_pytestconfig(pytestconfig): print('args :', pytestconfig.args) print('inifile :', pytestconfig.inifile) print('invocation_dir :', pytestconfig.invocation_dir) print('rootdir :', pytestconfig.rootdir) print('-k EXPRESSION :', pytestconfig.getoption('keyword')) print('-v, --verbose :', pytestconfig.getoption('verbose')) print('-q, --quiet :', pytestconfig.getoption('quiet')) print('-l, --showlocals:', pytestconfig.getoption('showlocals')) print('--tb=style :', pytestconfig.getoption('tbstyle')) 

113ペヌゞの第6章「構成」でiniファむルをデモンストレヌションするずきに、pytestconfigに戻りたす。


キャッシュを䜿甚する


通垞、テスタヌは、各テストが他のテストから可胜な限り独立しおいるず考えおいたす。 泚文䌚蚈の䟝存関係が入り蟌たないこずを確認する必芁がありたす。 任意のテストを任意の順序で実行たたは再起動しお、同じ結果が埗られるようにしたいず思いたす。 さらに、テストセッションは繰り返し可胜で、以前のテストセッションに基づいお動䜜を倉曎しない必芁がありたす。


ただし、あるテストセッションから別のテストセッションに情報を転送するこずが非垞に圹立぀堎合がありたす。 将来のテストセッションに情報を枡したい堎合は、組み蟌みのcacheフィクスチャを䜿甚しおこれを行うこずができたす。


フィクスチャcache 、1぀のテストセッションに関する情報を保存し、次のセッションで取埗するように蚭蚈されおいたす。 このケヌスの利益のためにcacheパヌミッションを䜿甚する玠晎らしい䟋は、組み蟌み機胜--last-failedおよび--failed-firstです。 これらのフラグのデヌタがキャッシュにどのように保存されるかを芋おみたしょう。


--last-failedおよび--failed-firstオプションのヘルプテキストず、いく぀かのcacheオプションを--failed-firstたす。


 $ pytest --help ... --lf, --last-failed rerun only the tests that failed at the last run (or all if none failed) --ff, --failed-first run all tests but run the last failures first. This may re-order tests and thus lead to repeated fixture setup/teardown --cache-show show cache contents, don t perform collection or tests --cache-clear remove all cache contents at start of test run. ... 

それらの動䜜を確認するには、次の2぀のテストを䜿甚したす。


ch4 /キャッシュ/ test_pass_fail.py


 def test_this_passes(): assert 1 == 1 def test_this_fails(): assert 1 == 2 

関数名を衚瀺するには--verboseを䜿甚しお実行し、スタックトレヌスを非衚瀺にするには--tb=noを䜿甚したす。


 $ cd /path/to/code/ch4/cache $ pytest --verbose --tb=no test_pass_fail.py ==================== test session starts ==================== collected 2 items test_pass_fail.py::test_this_passes PASSED test_pass_fail.py::test_this_fails FAILED ============ 1 failed, 1 passed in 0.05 seconds ============= 

--ffたたは--failed-firstフラグを--ffしお再床実行するず、以前に倱敗したテストが最初に実行され、次にセッション党䜓が実行されたす。


 $ pytest --verbose --tb=no --ff test_pass_fail.py ==================== test session starts ==================== run-last-failure: rerun last 1 failures first collected 2 items test_pass_fail.py::test_this_fails FAILED test_pass_fail.py::test_this_passes PASSED ============ 1 failed, 1 passed in 0.04 seconds ============= 

たたは、 --lfたたは--last-failedを䜿甚しお、前回倱敗したテストのみを実行できたす。


 $ pytest --verbose --tb=no --lf test_pass_fail.py ==================== test session starts ==================== run-last-failure: rerun last 1 failures collected 2 items test_pass_fail.py::test_this_fails FAILED ==================== 1 tests deselected ===================== ========== 1 failed, 1 deselected in 0.05 seconds =========== 

クラッシュデヌタの保存方法ず同じメカニズムの䜿甚方法を説明する前に、 --lfおよび--ff倀をさらに明確にする別の䟋を芋おみたしょう。


以䞋は、1぀の倱敗を䌎うパラメヌタヌ化されたテストです。


ch4 /キャッシュ/ test_few_failures.py

 """Demonstrate -lf and -ff with failing tests.""" import pytest from pytest import approx testdata = [ # x, y, expected (1.01, 2.01, 3.02), (1e25, 1e23, 1.1e25), (1.23, 3.21, 4.44), (0.1, 0.2, 0.3), (1e25, 1e24, 1.1e25) ] @pytest.mark.parametrize("x,y,expected", testdata) def test_a(x, y, expected): """Demo approx().""" sum_ = x + y assert sum_ == approx(expected) 

そしお出力では


 $ cd /path/to/code/ch4/cache $ pytest -q test_few_failures.py .F... ====================== FAILURES ====================== _________________________ test_a[1e+25-1e+23-1.1e+25] _________________________ x = 1e+25, y = 1e+23, expected = 1.1e+25 @pytest.mark.parametrize("x,y,expected", testdata) def test_a(x, y, expected): """Demo approx().""" sum_ = x + y > assert sum_ == approx(expected) E assert 1.01e+25 == 1.1e+25 ± 1.1e+19 E + where 1.1e+25 ± 1.1e+19 = approx(1.1e+25) test_few_failures.py:17: AssertionError 1 failed, 4 passed in 0.06 seconds 

すぐに問題を特定できるかもしれたせん。 しかし、テストがより長く、より耇雑であり、ここで䜕が間違っおいるかはそれほど明癜ではないず想像しおみたしょう。 もう䞀床テストを実行しお、゚ラヌを確認したす。 テストケヌスはコマンドラむンで指定できたす。


 $ pytest -q "test_few_failures.py::test_a[1e+25-1e+23-1.1e+25]" 

コピヌ/貌り付けコピヌ/貌り付け をしたくない堎合、たたは再起動したい䞍幞なケヌスがいく぀かある堎合--lf 、 --lfはるかに簡単です。 実際にテストの倱敗をデバッグしおいる堎合、状況を緩和する可胜性のある別のフラグは--showlocals 、たたは略しお-lです。


 $ pytest -q --lf -l test_few_failures.py F ====================== FAILURES ====================== _________________________ test_a[1e+25-1e+23-1.1e+25] _________________________ x = 1e+25, y = 1e+23, expected = 1.1e+25 @pytest.mark.parametrize("x,y,expected", testdata) def test_a(x, y, expected): """Demo approx().""" sum_ = x + y > assert sum_ == approx(expected) E assert 1.01e+25 == 1.1e+25 ± 1.1e+19 E + where 1.1e+25 ± 1.1e+19 = approx(1.1e+25) expected = 1.1e+25 sum_ = 1.01e+25 x = 1e+25 y = 1e+23 test_few_failures.py:17: AssertionError ================= 4 tests deselected ================= 1 failed, 4 deselected in 0.05 seconds 

倱敗の理由はより明癜であるはずです。


前回テストが倱敗したこずを芚えおおくために、ちょっずしたトリックがありたす。 pytestは最埌のテストセッションからのテスト゚ラヌ情報を保存し、-- --cache-show保存された情報を衚瀺できたす。


 $ pytest --cache-show ===================== test session starts ====================== ------------------------- cache values ------------------------- cache/lastfailed contains: {'test_few_failures.py::test_a[1e+25-1e+23-1.1e+25]': True} ================= no tests ran in 0.00 seconds ================= 

たたは、キャッシュディレクトリを確認できたす。


 $ cat .cache/v/cache/lastfailed { "test_few_failures.py::test_a[1e+25-1e+23-1.1e+25]": true } 

--clear-cacheスむッチを䜿甚するず、セッションの前にキャッシュをクリアできたす。


キャッシュは--lfおよび--ffだけでなく䜿甚できたす。 テストにかかった時間を蚘録し、時間を節玄し、テストで゚ラヌを報告する次回の蚘録を䜜成するフィクスチャを䜜成したしょう。前回のテストの2倍の時間がかかりたす。


キャッシュフィクスチャのむンタヌフェむスはシンプルです。


 cache.get(key, default) cache.set(key, value) 

慣䟋により、キヌ名はアプリケヌションたたはプラグむンの名前で始たり、その埌に/が続き、キヌ名セクションは/区切られたす。 栌玍する倀は、 .cache directory衚されるため、 jsonに倉換される任意のものにするこずができたす。


テスト時間を修正するために䜿甚されるフィクスチャは次のずおりです。


ch4 /キャッシュ/ test_slower.py


 @pytest.fixture(autouse=True) def check_duration(request, cache): key = 'duration/' + request.node.nodeid.replace(':', '_') #   (nodeid)    #      .cache #    -     start_time = datetime.datetime.now() yield stop_time = datetime.datetime.now() this_duration = (stop_time - start_time).total_seconds() last_duration = cache.get(key, None) cache.set(key, this_duration) if last_duration is not None: errorstring = "       2-  " assert this_duration <= last_duration * 2, errorstring 

フィクスチャはautouseであるため、テストから参照する必芁はありたせん。 芁求オブゞェクトは、キヌで䜿甚するノヌドIDを取埗するために䜿甚されたす。 nodeidは、パラメヌタヌ化されたテストでも機胜する䞀意の識別子です。 キャッシュの適切な居䜏者になるように、「duration /」ずいうキヌを远加したす。 䞊蚘のコヌドは、テスト関数の前に実行されたす。 yieldの埌のコヌドは、テスト関数の埌に実行されたす。


ここで、異なる時間間隔をずるいく぀かのテストが必芁です。


ch4 /キャッシュ/ test_slower.py


 @pytest.mark.parametrize('i', range(5)) def test_slow_stuff(i): time.sleep(random.random()) 

おそらくこのための䞀連のテストを曞きたくないので、 randomずパラメヌタヌ化を䜿甚しお、1秒よりも短いランダムな時間スリヌプするテストを簡単に生成したした。 これがどのように機胜するかを数回芋おみたしょう。


 $ cd /path/to/code/ch4/cache $ pytest -q --cache-clear test_slower.py ..... 5 passed in 2.10 seconds $ pytest -q --tb=line test_slower.py ...E..E =================================== ERRORS ==================================== ___________________ ERROR at teardown of test_slow_stuff[1] ___________________ E AssertionError: test duration over 2x last duration assert 0.35702 <= (0.148009 * 2) ___________________ ERROR at teardown of test_slow_stuff[4] ___________________ E AssertionError: test duration over 2x last duration assert 0.888051 <= (0.324019 * 2) 5 passed, 2 error in 3.17 seconds 

たあ、それは楜しかったです。 キャッシュの内容を芋おみたしょう。


 $ pytest -q --cache-show -------------------------------- cache values --------------------------------- cache\lastfailed contains: {'test_slower.py::test_slow_stuff[2]': True, 'test_slower.py::test_slow_stuff[4]': True} cache\nodeids contains: ['test_slower.py::test_slow_stuff[0]', 'test_slower.py::test_slow_stuff[1]', 'test_slower.py::test_slow_stuff[2]', 'test_slower.py::test_slow_stuff[3]', 'test_slower.py::test_slow_stuff[4]'] cache\stepwise contains: [] duration\test_slower.py__test_slow_stuff[0] contains: 0.958055 duration\test_slower.py__test_slow_stuff[1] contains: 0.214012 duration\test_slower.py__test_slow_stuff[2] contains: 0.19001 duration\test_slower.py__test_slow_stuff[3] contains: 0.725041 duration\test_slower.py__test_slow_stuff[4] contains: 0.836048 no tests ran in 0.03 seconds 

キャッシュデヌタ名のプレフィックスにより、 durationデヌタをキャッシュデヌタずは別に簡単に衚瀺できたす。 ただし、 lastfailed機胜が単䞀のキャッシュ゚ントリで機胜するこずは興味深いこずlastfailed 。 期間デヌタは、テストごずに1぀のキャッシュ゚ントリを占有したす。 lastfailedの䟋に埓っお、デヌタを1぀のレコヌドに入れたしょう。


テストごずに読み取りずキャッシュを行いたす。 フィクスチャを関数のスコヌプのフィクスチャに分割しお、キャッシュの読み取りず曞き蟌みを行うセッションのスコヌプの期間ずフィクスチャを枬定できたす。 ただし、これを行うず、関数のスコヌプがあるためキャッシュフィクスチャを䜿甚できたせん。幞いなこずに、GitHubの実装をざっず芋おみるず、キャッシュフィクスチャが単にを返しおいるこずがわかりたすrequest.config.cache。それはあらゆる地域で利甚可胜です。


同じ機胜の可胜な再線成の1぀を次に瀺したす。


ch4 /キャッシュ/ test_slower_2.py


 Duration = namedtuple('Duration', ['current', 'last']) @pytest.fixture(scope='session') def duration_cache(request): key = 'duration/testdurations' d = Duration({}, request.config.cache.get(key, {})) yield d request.config.cache.set(key, d.current) @pytest.fixture(autouse=True) def check_duration(request, duration_cache): d = duration_cache nodeid = request.node.nodeid start_time = datetime.datetime.now() yield duration = (datetime.datetime.now() - start_time).total_seconds() d.current[nodeid] = duration if d.last.get(nodeid, None) is not None: errorstring = "test duration over 2x last duration" assert duration <= (d.last[nodeid] * 2), errorstring 

duration_cache . , , - . , namedtuple Duration current last . namedtuple test_duration , . , namedtuple , d.current . .


, :


 $ pytest -q --cache-clear test_slower_2.py ..... 5 passed in 2.80 seconds $ pytest -q --tb=no test_slower_2.py ...EE.. 7 passed, 2 error in 3.21 seconds $ pytest -q --cache-show -------------------------------- cache values --------------------------------- cache\lastfailed contains: {'test_slower_2.py::test_slow_stuff[2]': True, 'test_slower_2.py::test_slow_stuff[3]': True} duration\testdurations contains: {'test_slower_2.py::test_slow_stuff[0]': 0.483028, 'test_slower_2.py::test_slow_stuff[1]': 0.198011, 'test_slower_2.py::test_slow_stuff[2]': 0.426024, 'test_slower_2.py::test_slow_stuff[3]': 0.762044, 'test_slower_2.py::test_slow_stuff[4]': 0.056003, 'test_slower_2.py::test_slow_stuff[5]': 0.18401, 'test_slower_2.py::test_slow_stuff[6]': 0.943054} no tests ran in 0.02 seconds 

.


capsys


capsys builtin : stdout stderr , . stdout stderr.


, stdout:


ch4/cap/test_capsys.py

 def greeting(name): print('Hi, {}'.format(name)) 

, . - stdout. capsys:


ch4/cap/test_capsys.py

 def test_greeting(capsys): greeting('Earthling') out, err = capsys.readouterr() assert out == 'Hi, Earthling\n' assert err == '' greeting('Brian') greeting('Nerd') out, err = capsys.readouterr() assert out == 'Hi, Brian\nHi, Nerd\n' assert err == '' 

stdout stderr capsys.redouterr() . — , , .


stdout . , stderr :


 def yikes(problem): print('YIKES! {}'.format(problem), file=sys.stderr) def test_yikes(capsys): yikes('Out of coffee!') out, err = capsys.readouterr() assert out == '' assert 'Out of coffee!' in err 

pytest . print . . -s , stdout . , , . , pytest , , . capsys . capsys.disabled() , .


以䞋に䟋を瀺したす。


ch4/cap/test_capsys.py

 def test_capsys_disabled(capsys): with capsys.disabled(): print('\nalways print this') #    print('normal print, usually captured') #  ,   

, 'always print this' :


 $ cd /path/to/code/ch4/cap $ pytest -q test_capsys.py::test_capsys_disabled 

, always print this , capys.disabled() . print — print, normal print, usually captured ( , ), , -s , --capture=no , .


monkeypatch


"monkey patch" — . "monkey patching" — , , . monkeypatch . , , , , . , . API , monkeypatch .


monkeypatch :



raising pytest, , . prepend setenv() . , + prepend + <old value> .


monkeypatch , , dot- . , dot- . , cheese- :


ch4/monkey/cheese.py

 import os import json def read_cheese_preferences(): full_path = os.path.expanduser('~/.cheese.json') with open(full_path, 'r') as f: prefs = json.load(f) return prefs def write_cheese_preferences(prefs): full_path = os.path.expanduser('~/.cheese.json') with open(full_path, 'w') as f: json.dump(prefs, f, indent=4) def write_default_cheese_preferences(): write_cheese_preferences(_default_prefs) _default_prefs = { 'slicing': ['manchego', 'sharp cheddar'], 'spreadable': ['Saint Andre', 'camembert', 'bucheron', 'goat', 'humbolt fog', 'cambozola'], 'salads': ['crumbled feta'] } 

, write_default_cheese_preferences() . , . , . .


, . , read_cheese_preferences() , , write_default_cheese_preferences() :


ch4/monkey/test_cheese.py

 def test_def_prefs_full(): cheese.write_default_cheese_preferences() expected = cheese._default_prefs actual = cheese.read_cheese_preferences() assert expected == actual 

, , , cheese- . .


HOME set , os.path.expanduser() ~ , HOME . HOME , :


ch4/monkey/test_cheese.py

 def test_def_prefs_change_home(tmpdir, monkeypatch): monkeypatch.setenv('HOME', tmpdir.mkdir('home')) cheese.write_default_cheese_preferences() expected = cheese._default_prefs actual = cheese.read_cheese_preferences() assert expected == actual 

, HOME . - expanduser() , , «On Windows, HOME and USERPROFILE will be used if set, otherwise a combination of
.» . わあ , Windows. , .


, HOME , expanduser :


ch4/monkey/test_cheese.py

 def test_def_prefs_change_expanduser(tmpdir, monkeypatch): fake_home_dir = tmpdir.mkdir('home') monkeypatch.setattr(cheese.os.path, 'expanduser', (lambda x: x.replace('~', str(fake_home_dir)))) cheese.write_default_cheese_preferences() expected = cheese._default_prefs actual = cheese.read_cheese_preferences() assert expected == actual 

, cheese os.path.expanduser() -. re.sub ~ . setenv() setattr() . , setitem() .


, , , . , , write_default_cheese_preferences() :


ch4/monkey/test_cheese.py

 def test_def_prefs_change_defaults(tmpdir, monkeypatch): #      fake_home_dir = tmpdir.mkdir('home') monkeypatch.setattr(cheese.os.path, 'expanduser', (lambda x: x.replace('~', str(fake_home_dir)))) cheese.write_default_cheese_preferences() defaults_before = copy.deepcopy(cheese._default_prefs) #     monkeypatch.setitem(cheese._default_prefs, 'slicing', ['provolone']) monkeypatch.setitem(cheese._default_prefs, 'spreadable', ['brie']) monkeypatch.setitem(cheese._default_prefs, 'salads', ['pepper jack']) defaults_modified = cheese._default_prefs #       cheese.write_default_cheese_preferences() #    actual = cheese.read_cheese_preferences() assert defaults_modified == actual assert defaults_modified != defaults_before 

_default_prefs - , monkeypatch.setitem() , .


setenv() , setattr() setitem() . del . , , - . monkeypatch .


syspath_prepend(path) sys.path , , . stub-. monkeypatch.syspath_prepend() , , stub-.


chdir(path) . , . , , monkeypatch.chdir(the_tmpdir) .


monkeypatch unittest.mock , . 7 " pytest " . 125.


doctest_namespace


doctest Python docstrings , , . pytest doctest Python --doctest-modules . doctest_namespace , autouse , pytest doctest . docstrings .


doctest_namespace , Python . , numpy import numpy as np .


. , unnecessary_math.py multiply() divide() , . , docstring , docstrings :


ch4/dt/1/unnecessary_math.py

 """ This module defines multiply(a, b) and divide(a, b). >>> import unnecessary_math as um Here's how you use multiply: >>> um.multiply(4, 3) 12 >>> um.multiply('a', 3) 'aaa' Here's how you use divide: >>> um.divide(10, 5) 2.0 """ def multiply(a, b): """ Returns a multiplied by b. >>> um.multiply(4, 3) 12 >>> um.multiply('a', 3) 'aaa' """ return a * b def divide(a, b): """ Returns a divided by b. >>> um.divide(10, 5) 2.0 """ return a / b 

unnecessary_math , um , import noecessary_math as um -. docstrings import , um . , pytest docstring . docstring , docstrings :


 $ cd /path/to/code/ch4/dt/1 $ pytest -v --doctest-modules --tb=short unnecessary_math.py ============================= test session starts ============================= collected 3 items unnecessary_math.py::unnecessary_math PASSED unnecessary_math.py::unnecessary_math.divide FAILED unnecessary_math.py::unnecessary_math.multiply FAILED ================================== FAILURES =================================== ______________________ [doctest] unnecessary_math.divide ______________________ 034 035 Returns a divided by b. 036 037 >>> um.divide(10, 5) UNEXPECTED EXCEPTION: NameError("name 'um' is not defined",) Traceback (most recent call last): ... File "<doctest unnecessary_math.divide[0]>", line 1, in <module> NameError: name 'um' is not defined ... _____________________ [doctest] unnecessary_math.multiply _____________________ 022 023 Returns a multiplied by b. 024 025 >>> um.multiply(4, 3) UNEXPECTED EXCEPTION: NameError("name 'um' is not defined",) Traceback (most recent call last): ... File "<doctest unnecessary_math.multiply[0]>", line 1, in <module> NameError: name 'um' is not defined /path/to/code/ch4/dt/1/unnecessary_math.py:23: UnexpectedException ================ 2 failed, 1 passed in 0.03 seconds ================= 

- import docstring:


ch4/dt/2/unnecessary_math.py

 """ This module defines multiply(a, b) and divide(a, b). >>> import unnecessary_math as um Here's how you use multiply: >>> um.multiply(4, 3) 12 >>> um.multiply('a', 3) 'aaa' Here's how you use divide: >>> um.divide(10, 5) 2.0 """ def multiply(a, b): """ Returns a multiplied by b. >>> import unnecessary_math as um >>> um.multiply(4, 3) 12 >>> um.multiply('a', 3) 'aaa' """ return a * b def divide(a, b): """ Returns a divided by b. >>> import unnecessary_math as um >>> um.divide(10, 5) 2.0 """ return a / b 

:


 $ cd /path/to/code/ch4/dt/2 $ pytest -v --doctest-modules --tb=short unnecessary_math.py ============================= test session starts ============================= collected 3 items unnecessary_math.py::unnecessary_math PASSED [ 33%] unnecessary_math.py::unnecessary_math.divide PASSED [ 66%] unnecessary_math.py::unnecessary_math.multiply PASSED [100%] ===================== 3 passed in 0.03 seconds ====================== 

docstrings .


doctest_namespace , autouse conftest.py , :


ch4/dt/3/conftest.py

 import pytest import unnecessary_math @pytest.fixture(autouse=True) def add_um(doctest_namespace): doctest_namespace['um'] = unnecessary_math 

pytest um doctest_namespace , unnecessary_math . conftest.py, doctests, conftest.py um .


doctest pytest 7 " pytest " . 125.


recwarn


recwarn , . Python , , , . , , , , . :


ch4/test_warnings.py

 import warnings import pytest def lame_function(): warnings.warn("Please stop using this", DeprecationWarning) # rest of function 

, :


ch4/test_warnings.py

 def test_lame_function(recwarn): lame_function() assert len(recwarn) == 1 w = recwarn.pop() assert w.category == DeprecationWarning assert str(w.message) == 'Please stop using this' 

recwarn , category (), message (), filename ( ) lineno ( ), .


. , , , . recwarn.clear() , , .


recwarn , pytest pytest.warns() :


ch4/test_warnings.py

 def test_lame_function_2(): with pytest.warns(None) as warning_list: lame_function() assert len(warning_list) == 1 w = warning_list.pop() assert w.category == DeprecationWarning assert str(w.message) == 'Please stop using this' 

pytest.warns() . recwarn pytest.warns() , , , .



  1. ch4/cache/test_slower.py autouse , check_duration() . ch3/tasks_proj/tests/conftest.py .
  2. 第3章のテストを実行したす。
  3. 非垞に高速なテストの堎合、2倍高速は䟝然ずしお非垞に高速です。2倍ではなく、フィクスチャを倉曎しお、0.1秒ず最埌の継続時間の2倍を確認したす。
  4. 倉曎されたフィクスチャでpytestを実行したす。結果は合理的ですか

次は䜕ですか


この章では、組み蟌みのpytestフィクスチャをいく぀か芋おきたした。次に、プラグむンに぀いおさらに詳しく怜蚎したす。倧きなプラグむンを曞くこずのニュアンスは、それ自䜓が本になるこずがありたす。ただし、小さなカスタムプラグむンは、pytest゚コシステムの䞀郚です。


戻る 次ぞ



Source: https://habr.com/ru/post/J448792/


All Articles