简体   繁体   English

Flask + Pytest + SQLAlchemy:使用flask-sqlalchemy运行pytest时无法创建和删除表

[英]Flask+Pytest+SQLAlchemy: Can't create and drop tables when run pytest using flask-sqlalchemy

when I run tests It succeeds to connect to the database, but it does not create tables. 当我运行测试它成功连接到数据库,但它不会创建表。 I think maybe there is a different way to create tables when I use flask-sqlalchemy, but I can't find the solution. 我想在使用flask-sqlalchemy时可能有不同的方法来创建表,但我找不到解决方案。

This is app.py 这是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

This is run.py 这是run.py

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


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

This is config.py 这是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

This is models.py 这是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()

This is test config setting. 这是测试配置设置。

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()

The following might be the problem that is preventing your code from running multiple times and/or preventing you from dropping/creating your tables. 以下可能是阻止您的代码多次运行和/或阻止您删除/创建表的问题。 Regardless if it solves your problem, it is something one might not be aware of and quite important to keep in mind. 无论它是否解决了您的问题,这都是人们可能不知道的事情,并且要记住这一点非常重要。 :) :)

When you are running your tests multiple times, db.drop_all() might not be called (because one of your tests failed) and therefore, it might not be able to create the tables on the next run (since they are already existing). 当您多次运行测试时,可能不会调用db.drop_all() (因为其中一个测试失败),因此,它可能无法在下次运行时创建表(因为它们已经存在)。 The problem lies in using a context manager without a try: finally: . 问题在于没有try: finally:使用上下文管理器try: finally: . (NOTE: Every fixture using yield is a context manager). (注意:使用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')

This code represents your code, but without using the functionality of pytest. 此代码代表您的代码,但不使用pytest的功能。 Pytest is running it more or less like Pytest或多或少都在运行它

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

One would expect an output similar to: 人们会期望输出类似于:

before
begin_foo
after
Test failed

but we only get 但我们只能得到

before
begin_foo
Test failed

While the contextmanager is active ( yield has been executed), our test method is running. 当contextmanager处于活动状态( yield已执行)时,我们的测试方法正在运行。 If an exception is raised during the execution of our test function, the execution is stopped WITHOUT running any code after the yield statement. 如果在执行测试函数期间引发异常,则执行将在yield语句之后运行任何代码而停止。 To prevent this, we have to wrap our fixture / contextmanager in a try: ... finally: block. 为了防止这种情况,我们必须在try: ... finally: block中包装我们的fixture / contextmanager As finally is ALWAYS executed regardless of what has happened. 最后总是执行,无论发生了什么。

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

The code after the yield statement is now executed as expected. yield语句之后的代码现在按预期执行。

before
begin foo
after
Test failed

If you want to learn more, see the relevant section in the contextmanager docs : 如果您想了解更多信息,请参阅contextmanager文档中的相关部分:

At the point where the generator yields, the block nested in the with statement is executed. 在生成器生成的点处,执行嵌套在with语句中的块。 The generator is then resumed after the block is exited. 然后在退出块之后恢复发生器。 If an unhandled exception occurs in the block, it is reraised inside the generator at the point where the yield occurred. 如果块中发生未处理的异常,则在生成器发生的点处将其重新加入。 Thus, you can use a try…except…finally statement to trap the error (if any), or ensure that some cleanup takes place. 因此,您可以使用try ... except ... finally语句来捕获错误(如果有),或确保进行一些清理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM