Flask + Pytest + SQLAlchemy: невозможно создавать и удалять таблицы при запуске pytest с использованием flask-sqlalchemy - PullRequest
3 голосов
/ 08 июля 2019

, когда я запускаю тесты. Он успешно подключается к базе данных, но не создает таблицы. Я думаю, может быть, есть другой способ создания таблиц, когда я использую флеш-sqlalchemy, но я не могу найти решение.

Это app.py

db = SQLAlchemy()
def create_app(config_name):
    app = Flask(__name__, template_folder='templates')
    app.wsgi_app = ProxyFix(app.wsgi_app)
    app.config.from_object(config_name)
    app.register_blueprint(api)
    db.init_app(app)

    @app.route('/ping')
    def health_check():
        return jsonify(dict(ok='ok'))

    @app.errorhandler(404)
    def ignore_error(err):
        return jsonify()

    app.add_url_rule('/urls', view_func=Shorty.as_view('urls'))
    return app

Это run.py

environment = environ['TINY_ENV']
config = config_by_name[environment]
app = create_app(config)


if __name__ == '__main__':
    app.run()

Это config.py

import os

basedir = os.path.abspath(os.path.dirname(__file__))


class Config:
    """
    set Flask configuration vars
    """
    # General config
    DEBUG = True
    TESTING = False
    # Database
    SECRET_KEY = os.environ.get('SECRET_KEY', 'my_precious_secret_key')
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root@localhost:3306/tiny'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SERVER_HOST = 'localhost'
    SERVER_PORT = '5000'


class TestConfig(Config):
    """
    config for test
    """
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root@localhost:3306/test_tiny'

config_by_name = dict(
    test=TestConfig,
    local=Config
)

key = Config.SECRET_KEY

Это models.py

from datetime import datetime
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


class URLS(db.Model):
    __tablename__ = 'urls'

    id = db.Column(db.Integer, primary_key=True)
    original_url = db.Column(db.String(400), nullable=False)
    short_url = db.Column(db.String(200), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow()

Это настройка конфигурации теста.

db = SQLAlchemy()


@pytest.fixture(scope='session')
def app():
    test_config = config_by_name['test']
    app = create_app(test_config)

    app.app_context().push()
    return app


@pytest.fixture(scope='session')
def client(app):
    return app.test_client()


@pytest.fixture(scope='session')
def init_db(app):
    db.init_app(app)
    db.create_all()
    yield db
    db.drop_all()

1 Ответ

0 голосов
/ 18 июля 2019

Следующая проблема может быть связана с тем, что код не запускается несколько раз и / или не позволяет вам удалять / создавать таблицы.Независимо от того, решит ли это вашу проблему, это то, о чем вы, возможно, не знаете, и очень важно помнить.:)

Когда вы запускаете свои тесты несколько раз, db.drop_all() может не вызываться (потому что один из ваших тестов не прошел) и, следовательно, он не сможет создать таблицы при следующем запуске (так какони уже существуют).Проблема заключается в использовании контекстного менеджера без try: finally:.(ПРИМЕЧАНИЕ. Каждый прибор, использующий yield, является контекстным менеджером).

from contextlib import contextmanager

def test_foo(db):
    print('begin foo')
    raise RuntimeError()
    print('end foo')

@contextmanager
def get_db():
    print('before')
    yield 'DB object'
    print('after')

Этот код представляет ваш код, но без использования функциональности pytest.Pytest запускает его более или менее как

try:
    with get_db(app) as db:
        test_foo(db)
except Exception as e:
    print('Test failed')

Можно ожидать, что результат будет похож на:

before
begin_foo
after
Test failed

, но мы получим только

before
begin_foo
Test failed

Пока контекстный менеджерактивен (yield был выполнен), наш тестовый метод запущен.Если во время выполнения нашей тестовой функции возникает исключение, выполнение останавливается БЕЗ выполнения какого-либо кода после оператора yield.Чтобы предотвратить это, мы должны обернуть наш fixture / contextmanager в блок try: ... finally:.Поскольку finally выполняется ВСЕГДА независимо от того, что произошло.

@contextmanager
def get_db():
    print('before')
    try:
        yield 'DB object'
    finally:
        print('after')

Код после оператора yield теперь выполняется должным образом.

before
begin foo
after
Test failed

Есливы хотите узнать больше, см. соответствующий раздел в contextmanager docs :

В момент, когда генератор сдается, выполняется блок, вложенный в оператор with.Затем генератор возобновляется после выхода из блока.Если в блоке возникает необработанное исключение, оно повторно вызывается внутри генератора в точке, где произошел выход.Таким образом, вы можете использовать оператор try… кроме… finally, чтобы перехватить ошибку (если она есть) или убедиться, что какая-то очистка произошла.

...