æ»ã 次ãž
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
ããè¿ãããå€ã¯ãã¿ã€ã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):
æåã®è¡ã¯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 = [
ãããŠåºåã§ã¯ïŒ
$ 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')
, '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
:
setattr(target, name, value=<notset>, raising=True)
: .delattr(target, name=<notset>, raising=True)
: .setitem(dic, name, value)
: .delitem(dic, name, raising=True)
: .setenv(name, value, prepend=None)
: .delenv(name, raising=True)
: .syspath_prepend(path)
: sys., Python.chdir(path)
: .
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):
_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)
, :
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()
, , , .
ch4/cache/test_slower.py
autouse
, check_duration()
. ch3/tasks_proj/tests/conftest.py
.- 第3ç« ã®ãã¹ããå®è¡ããŸãã
- éåžžã«é«éãªãã¹ãã®å Žåã2åé«éã¯äŸç¶ãšããŠéåžžã«é«éã§ãã2åã§ã¯ãªãããã£ã¯ã¹ãã£ãå€æŽããŠã0.1ç§ãšæåŸã®ç¶ç¶æéã®2åã確èªããŸãã
- å€æŽããããã£ã¯ã¹ãã£ã§pytestãå®è¡ããŸããçµæã¯åççã§ããïŒ
次ã¯äœã§ãã
ãã®ç« ã§ã¯ãçµã¿èŸŒã¿ã®pytestãã£ã¯ã¹ãã£ãããã€ãèŠãŠããŸããã次ã«ããã©ã°ã€ã³ã«ã€ããŠããã«è©³ããæ€èšããŸãã倧ããªãã©ã°ã€ã³ãæžãããšã®ãã¥ã¢ã³ã¹ã¯ãããèªäœãæ¬ã«ãªãããšããããŸãããã ããå°ããªã«ã¹ã¿ã ãã©ã°ã€ã³ã¯ãpytestãšã³ã·ã¹ãã ã®äžéšã§ãã
æ»ã 次ãž