Pytest запускает все тесты дважды и сравнивает результаты ставок макетных и реальных - PullRequest
2 голосов
/ 12 июня 2019

Я пишу какой-нибудь плагин в БД - который может изменить результаты, полученные от БД, но в большинстве случаев это не ожидается.Я хочу знать, когда это произойдет.

У меня есть несколько десятков тестов, и я добавляю больше для любой функции, и я хотел бы иметь систему, в которой все тесты, выполняемые один раз, аганируют БД без этого плагина, изатем с плагином и возможностью сравнить результаты.Мне нужно, чтобы он был готов к расширению с помощью дополнительных тестов.

В настоящее время я могу изменить в приспособлении, если DB будет работать с плагином или без него.есть ли возможность заставить тесты запускаться дважды при каждом запуске с разными приборами?

1 Ответ

2 голосов
/ 12 июня 2019

Если я не понял ваш вопрос, вы можете определить параметризованный прибор, который выберет конкретный импл на основе текущего параметра (реального или ложного).Вот рабочий пример использования 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, если у вас есть непараметризованные тесты и т. д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...