Fixtures are powerful:
Assume you know:
You should know this:
import pytest @pytest.fixture def foo(): return 42 def test_foo(foo): assert foo == 42 class TestBar: @pytest.fixture def bar(self, request): def fin(): print('Teardown of fixture bar') request.addfinalizer(fin) return 7 def test_bar(self, foo, bar): assert foo != bar
Fixture decorator has scope argument:
@pytest.fixture(scope='session') def foo(request): print('session setup') def fin(): print('session finalizer') request.addfinalizer(fin) return f @pytest.fixture(scope='function') # default scope def bar(request): print('funtion setup') def fin(): print('function finalizer') request.addfinalizer(fin) return b def test_one(foo, bar): pass def test_two(foo, bar): pass
$ py.test test_ex.py -s ========================== test session starts ========================== platform linux2 -- Python 2.7.6 -- py-1.4.20 -- pytest-2.5.2 plugins: timeout, capturelog, xdist collected 2 items test_ex.py session setup funtion setup .function finalizer function setup .function finalizer session finalizer ======================= 2 passed in 0.07 seconds ======================== Process *pytest* finished
Available scopes: function, class, module, session
Fixture can use fixtures too:
@pytest.fixture(scope='session') def db_conn(): return create_db_conn() @pytest.fixture(scope='module') def db_table(request, db_conn): table = create_table(db_conn, 'foo') def fin(): drop_table(db_conn, table) request.addfinalizer(fin) return table def test_bar(db_table): pass
Fixtures can trigger skipping/failing of all dependent tests:
@pytest.fixture(scope='session') def redis_client(): servers = ['localhost', 'venera.clockhouse'] for hostname in servers: try: return redis.StrictRedis(hostname) except redis.ConnectionError: continue else: pytest.skip('No Redis server found')
Rember tests can be marked:
@pytest.mark.mymarker @pytest.mark.other_marker def test_something(): pass
Run tests based on markers:
$ py.test -m "not mymarker"
Make them strict:
# pytest.ini [pytest] addopts = --strict markers = mymarker: a custom marker
@pytest.fixture def mongo_client(request): marker = request.node.get_marker('mongo_db') if not marker: db = 'TestDB' else: def apifun(db): return db db = apifun(*marker.args, **marker.kwargs) return pymongo.MongoClient('127.0.0.1/{}'.format(db)) @pytest.mark.mongo_db('Users') def test_something(mongo_client): pass
Setup/teardown without explicit request:
@pytest.mark.linux def test_mem_stack(): assert MemSizes().stack == 42 @pytest.fixture(autouse=True) def _platform_skip(request): marker = request.node.get_marker('linux') if marker and platform.system() != 'Linux': pytest.skip('N/A on {}'.format(platform.system()))
@pytest.fixture(params=['ora', 'pg', 'sqlite']) def dburi(request): return create_db_uri(request.param) @pytest.fixture(params=['ipv4', 'ipv6']) def addr_family(request): return socket.AF_INET if request.param == 'ipv4' else socket.AF_INET6 def test_txn(dburi): inst = MyObj(dburi) assert inst.transaction_works() def test_conn(dburi, addr_family): inst = MyObj(dburi, addr_family) assert inst.it_works()
Skipping can be done on a parameter level:
try: import cx_Oracle as ora except ImportError: ora = None needs_ora = pytest.mark.skipif(ora is None, reason='No Oracle installed') @pytest.fixture(params=[ 'pg', needs_ora('ora'), ]) def dburi(request): return create_db_uri(request.param)
Find out what other fixtures are requested:
@pytest.fixture def db(request): if 'transactional_db' in request.fixturenames: pytest.fail('Conflicting fixtures') return no_transactions_db() @pytest.fixture def transactional_db(request): if 'db' in request.fixturenames: pytest.fail('Conflicting fixtures') return transactional_db()
myproj/ +- myproj/ | +- __init__.py | +- models.py +- tests/ +- contest.py +- test_models.py
A few common hooks:
pytest_namespace()
pytest_addoption(parser)
pytest_ignore_collect(path, config)
pytest_sessionstart(session)
pytest_sessionfinish(session, exitstatus)
pytest_assertrepr_compare(config, op, left, right)
See hookspec for full list
New options an be accessed from fixtures and tests:
#conftest.py def pytest_addoption(parser): parser.addoption('--ci', action='store_true', help='Indicate tests are run on CI server') @pytest.fixture def fix(request): ci = request.config.getoption('ci') # Test module def test_foo(pytestconfig): ci = pytestconfig.getoption('ci')
Skipping not allowed on CI server:
@pytest.fixture(scope='session') def redis_client(request): servers = ['localhost', 'venera.clockhouse'] for hostname in servers: try: return redis.StrictRedis(hostname) except redis.ConnectionError: continue else: if request.config.getoption('ci'): pytest.fail('No Redis server found') else: pytest.skip('No Redis server found')
Thanks for listening!
flub@devork.be
@flubdevork