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

时间:2019-07-08 16:48:20

标签: python flask sqlalchemy pytest

运行测试时,它可以成功连接到数据库,但是不会创建表。我认为当我使用flask-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 :(得分:0)

以下可能是阻止您的代码多次运行和/或阻止您删除/创建表的问题。不管它能否解决您的问题,这都是人们可能不了解的,并且牢记在心非常重要。 :)

当您多次运行测试时,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

当contextmanager处于活动状态(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 ... except ... finally语句进行陷阱   错误(如果有),或确保进行一些清理。