Если я не понял ваш вопрос, вы можете определить параметризованный прибор, который выберет конкретный импл на основе текущего параметра (реального или ложного).Вот рабочий пример использования sqlalchemy
с базой данных SQLite и alchemy-mock
:
import pytest
from unittest import mock
from sqlalchemy import create_engine, Column, String, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from alchemy_mock.mocking import UnifiedAlchemyMagicMock
Base = declarative_base()
class Item(Base):
__tablename__ = 'items'
id = Column(Integer, primary_key=True)
name = Column(String)
@pytest.fixture
def real_db_session():
engine = create_engine('sqlite:///real.db')
with engine.connect() as conn:
Session = sessionmaker(bind=conn)
Base.metadata.create_all(engine)
session = Session()
sample_item = Item(name='fizz')
session.add(sample_item)
session.commit()
yield session
@pytest.fixture
def mocked_db_session():
session = UnifiedAlchemyMagicMock()
session.add(Item(name='fizz'))
return session
@pytest.fixture(params=('real', 'mock'))
def db_session(request, real_db_session, mocked_db_session):
backend_type = request.param
if backend_type == 'real':
return real_db_session
elif backend_type == 'mock':
return mocked_db_session
Тестовый пример:
def test_fizz(db_session):
assert db_session.query(Item).one().name == 'fizz'
Результаты выполнения:
$ pytest -v
======================================= test session starts ========================================
platform linux -- Python 3.6.8, pytest-4.4.2, py-1.8.0, pluggy-0.11.0
cachedir: .pytest_cache
rootdir: /home/hoefling/projects/private/stackoverflow/so-56558823
plugins: xdist-1.28.0, forked-1.0.2, cov-2.7.1
collected 2 items
test_spam.py::test_fizz[real] PASSED [ 50%]
test_spam.py::test_fizz[mock] PASSED [100%]
===================================== 2 passed in 0.18 seconds =====================================
Пример: пользовательский порядок выполнения
Вам потребуется реализовать пользовательский хук pytest_collection_modifyitems
, где вы можете использовать список собранных тестов.Например, чтобы сначала запустить real
тесты, а затем остальные:
# conftest.py
def pytest_collection_modifyitems(session, config, items):
items.sort(key=lambda item: 'real' in item.name, reverse=True)
Пример: собрать и оценить результаты тестов
Этот пример основан на моем ответе на вопрос Как я могу получить доступ к общему результату теста Pytest во время выполнения? .Слабое следование этому:
# conftest.py
def pytest_sessionstart(session):
session.results = dict()
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
result = outcome.get_result()
if result.when == 'call':
item.session.results[item] = result
@pytest.fixture(scope='session', autouse=True)
def compare_results(request):
yield # wait for all tests to finish
results = request.session.results
# partition test results into reals and mocks
def partition(pred, coll):
first, second = itertools.tee(coll)
return itertools.filterfalse(pred, first), filter(pred, second)
mocks, reals = partition(lambda item: item.name.endswith('[real]'), results.keys())
# process test results in pairs
by_name = operator.attrgetter('name')
for real, mock in zip(sorted(reals, key=by_name), sorted(mocks, key=by_name)):
if results[real].outcome != results[mock].outcome:
pytest.fail(
'A pair of tests has different outcomes:\n'
f'outcome of {real.name} is {results[real].outcome}\n'
f'outcome of {mock.name} is {results[mock].outcome}'
)
Конечно, это всего лишь заглушка;например, сравнение не удастся выполнить в первой паре тестов с разными результатами, также разделение ключей диктов результатов даст неровные списки для reals
и mocks
, если у вас есть непараметризованные тесты и т. д.