Как использовать pytest для модульного тестирования классов sqlalchemy orm - PullRequest
6 голосов
/ 01 ноября 2019

Я хочу написать некоторый код py.test для тестирования 2 простых классов ORM sqlalchemy, которые были созданы на основе этого урока . Проблема в том, как установить базу данных в py.test для тестовой базы данных и откатить все изменения после завершения тестов? Можно ли макетировать базу данных и запускать тесты без фактического подключения к базе данных?

вот код для моих классов:


from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker, relationship

eng = create_engine('mssql+pymssql://user:pass@host/my_database')

Base = declarative_base(eng)
Session = sessionmaker(eng)
intern_session = Session()

class Author(Base):
    __tablename__ = "Authors"

    AuthorId = Column(Integer, primary_key=True)
    Name = Column(String)  
    Books = relationship("Book")

    def add_book(self, title):
        b = Book(Title=title, AuthorId=self.AuthorId)
        intern_session.add(b)
        intern_session.commit()

class Book(Base):
    __tablename__ = "Books"

    BookId = Column(Integer, primary_key=True)
    Title = Column(String)      
    AuthorId = Column(Integer, ForeignKey("Authors.AuthorId"))    

    Author = relationship("Author")                           

1 Ответ

1 голос
/ 01 ноября 2019

Я обычно так делаю:

  1. Я не создаю экземпляр движка и сеанса с объявлениями модели, вместо этого я объявляю только Base без привязки:

    Base = declarative_base()
    

    и я создаю сеанс только при необходимости с

    engine = create_engine('<the db url>')
    db_session = sessionmaker(bind=engine)
    

    Вы можете сделать то же самое, не используя intern_session в вашем методе add_book, а вместо этого используйте параметр session.

    def add_book(self, session, title):
        b = Book(Title=title, AuthorId=self.AuthorId)
        session.add(b)
        session.commit()
    

    Это делает ваш код более тестируемым, так как теперь вы можете пропустить выбранный вами сеанс при вызове метода. И вы больше не застряли с сеансом, привязанным к жестко закодированному URL базы данных.

  2. Добавьте пользовательскую опцию --dburl в pytest, используя pytest_addoption hook .

    Просто добавьте это на свой верхний уровень conftest.py:

    def pytest_addoption(parser):
        parser.addoption('--dburl',
                         action='store',
                         default='<if needed, whatever your want>',
                         help='url of the database to use for tests')
    

    Теперь вы можете запустить pytest --dburl <url of the test database>

  3. Затем вы можете получитьопция dburl из приспособления request

    • Из пользовательского приспособления:

    @pytest.fixture()
    def db_url(request):
        return request.config.getoption("--dburl")
        # ...
    
    • Внутри теста:

    def test_something(request):
        db_url = request.config.getoption("--dburl")
        # ...
    

На данный момент вы можете:

  • получить тест db_url в любом тесте или приборе
  • использовать его для создания механизма
  • создать сеанс, связанный с механизмом
  • передать сеанс тестируемому методу

Делать это в каждом тесте довольно сложно, поэтому вы можете использовать полезные приборы pytest для упрощения процесса.

Ниже приведены некоторые приборы, которые я использую:

@pytest.fixture(scope='session')
def db_engine(request):
    """yields a SQLAlchemy engine which is suppressed after the test session"""
    db_url = request.config.getoption("--dburl")
    engine_ = create_engine(db_url, echo=True)

    yield engine_

    engine_.dispose()


@pytest.fixture(scope='session')
def db_session_factory(db_engine):
    """returns a SQLAlchemy scoped session factory"""
    return scoped_session(sessionmaker(bind=engine))


@pytest.fixture(scope='function')
def db_session(db_session_factory):
    """yields a SQLAlchemy connection which is rollbacked after the test"""
    session_ = session_factory()

    yield session_

    session_.rollback()
    session_.close()

Используя приспособление db_session, вы можете получитьч и очистить db_session для каждого теста. Когда тест завершается, db_session откатывается, сохраняя чистоту базы данных.

...