Python 如何使用SQLAlchemy设置用于测试的Flask应用程序?

Python 如何使用SQLAlchemy设置用于测试的Flask应用程序?,python,unit-testing,testing,sqlalchemy,flask,Python,Unit Testing,Testing,Sqlalchemy,Flask,在烧瓶中,通常的做法是这样开始: from flask import Flask from flaskext.sqlalchemy import SQLAlchemy app = Flask(__name__) SQLALCHEMY_DATABASE_URI = 'something' app.config.from_object(__name__) db = SQLAlchemy(app) import os import myapp import unittest import tempf

在烧瓶中,通常的做法是这样开始:

from flask import Flask
from flaskext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
SQLALCHEMY_DATABASE_URI = 'something'
app.config.from_object(__name__)
db = SQLAlchemy(app)
import os
import myapp
import unittest
import tempfile

class MyappTestCase(unittest.TestCase):

    def setUp(self):
        self.db_fd, myapp.app.config['DATABASE'] = tempfile.mkstemp()
        self.app = myapp.app.test_client()
        myapp.init_db()

    def tearDown(self):
        os.close(self.db_fd)
        os.unlink(myapp.app.config['DATABASE'])

    def test_empty_db(self):
        rv = self.app.get('/')
        assert 'No entries here so far' in rv.data
然后在任何地方导入并使用
app
db
。但是,当您像这样创建
db
时,它会从应用程序中获取配置,并且似乎一旦发生这种配置,就永远无法覆盖它。Flask的网站上有一些关于制作应用程序工厂的页面,但不清楚如果我这样做的话,我将如何继续在任何地方使用
app
db


如何编写脚本以使用不同的数据库测试Flask应用程序?我应该如何构造我的应用程序来实现这一点?是否必须使用
模块
s?

您不希望在导入时连接到数据库。继续在导入时配置应用程序,因为在尝试测试或运行应用程序之前,您可以在测试中调整配置。在下面的示例中,您将在一些使用应用程序配置的函数后面使用db连接,因此在unittest中,您可以实际更改db连接以指向不同的文件,然后继续并在设置中显式连接

假设您有一个包含myapp.py的myapp包,它看起来像:

# myapp/myapp.py
from __future__ import with_statement
from sqlite3 import dbapi2 as sqlite3
from contextlib import closing
from flask import Flask, request, session, g, redirect, url_for, abort, \
     render_template, flash

# configuration
DATABASE = '/tmp/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'

# create our little application :)
app = Flask(__name__)
app.config.from_object(__name__)
app.config.from_envvar('MYAPP_SETTINGS', silent=True)

def connect_db():
    """Returns a new connection to the database."""
    return sqlite3.connect(app.config['DATABASE'])


def init_db():
    """Creates the database tables."""
    with closing(connect_db()) as db:
        with app.open_resource('schema.sql') as f:
            db.cursor().executescript(f.read())
        db.commit()


@app.before_request
def before_request():
    """Make sure we are connected to the database each request."""
    g.db = connect_db()


@app.after_request
def after_request(response):
    """Closes the database again at the end of the request."""
    g.db.close()
    return response

@app.route('/')
def show_entries():
    cur = g.db.execute('select title, text from entries order by id desc')
    entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()]
    return render_template('show_entries.html', entries=entries)

if __name__=="__main__":
    app.run()
您的测试文件myapp/test\u myapp.py将如下所示:

from flask import Flask
from flaskext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
SQLALCHEMY_DATABASE_URI = 'something'
app.config.from_object(__name__)
db = SQLAlchemy(app)
import os
import myapp
import unittest
import tempfile

class MyappTestCase(unittest.TestCase):

    def setUp(self):
        self.db_fd, myapp.app.config['DATABASE'] = tempfile.mkstemp()
        self.app = myapp.app.test_client()
        myapp.init_db()

    def tearDown(self):
        os.close(self.db_fd)
        os.unlink(myapp.app.config['DATABASE'])

    def test_empty_db(self):
        rv = self.app.get('/')
        assert 'No entries here so far' in rv.data

当然,如果您想使用SQLAlchemy,您必须适当地更新connect\u db和init\u db函数,但希望您能理解。

您使用环境变量的本能是正确的。但是,使用错误的db运行单元测试存在一些危险。此外,您可能不想将数据库连接到每个请求以及您想使用数据库的任何地方。可以使用显式设置的配置目录和环境变量。这是到目前为止我想到的最好的

run.py
shell.py

config/__init__.py
config/test.py
config/postgres.py
...

main/__init__.py
main/someapp/__init__.py
main/someapp/models.py

...
main/tests/__init__.py
main/tests/testutils.py
因此,配置文件可能是:

# config/test.py
SQLALCHEMY_DATABASE_URI = "sqlite://"

因此,我可以在基本测试用例中显式设置db:

import os
from flask.ext.testing import TestCase

os.environ["DIAG_CONFIG_MODULE"] = "config.test"
from main import app, db


class SQLAlchemyTest(TestCase):

    def create_app(self):
        return app

    def setUp(self):
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()
然后,
main/\uuuu init\uuuu.py
,对我来说:

import os

from flask import Flask, render_template, g
from flask.ext.sqlalchemy import SQLAlchemy

# by default, let's use a DB we don't care about
# but, we can override if we want
config_obj = os.environ.get("DIAG_CONFIG_MODULE", "config.test")
app = Flask(__name__)
app.config.from_object(config_obj)
db = SQLAlchemy(app)

@app.before_request
def before_request():
    g.db = db
    g.app = app

# ...
@app.route('/', methods=['GET'])
def get():
    return render_template('home.html')
# ...    
from main.someapp.api import mod as someappmod
app.register_blueprint(someappmod)
然后,在我知道要运行的配置的其他文件中,可能会:

# run.py
import os
os.environ["DIAG_CONFIG_MODULE"] = "config.postgres"
from main import app
app.run(debug=True)


也许。。到目前为止最好的:P.

首先,不要直接在脚本中实例化Flask应用程序,而是使用一个。这意味着您创建了一个以配置文件为参数的函数,并返回实例化的应用程序对象。然后,创建不带参数的全局SQLAlchemy对象,并在创建应用程序时对其进行配置

要运行应用程序,只需执行以下操作:

app = create_app('config.py')
app.run()
class Test(TestCase):
    def setUp(self):
        # init test database, etc.
        app = create_app('test_config.py')
        self.app = app.test_client()
    def tearDown(self):
        # delete test database, etc.
要运行单元测试,可以执行以下操作:

app = create_app('config.py')
app.run()
class Test(TestCase):
    def setUp(self):
        # init test database, etc.
        app = create_app('test_config.py')
        self.app = app.test_client()
    def tearDown(self):
        # delete test database, etc.
在我的例子中,我将SQLAlchemy直接用于作用域_会话,而不是Flask SQLAlchemy。
我也这么做了,但同时使用了。

,我将使用app.config.from_envvar,并在以不同方式运行我的应用程序时始终在命令行上指定配置文件。这似乎是一个解决方案,但如果您忘了使用错误的db运行测试…Mike:在该代码段中,每次有请求时,它都会建立连接。你不认为这会因为连接调用的巨大开销而降低应用程序的速度吗?是的,这并不理想。对于sqlite3,我怀疑您是否会注意到,但是对于连接到网络数据库(mysql、postgresql、mssql、mongo、cassandra等)的连接,您需要一个连接池来减少开销。:)因为我在使用mysql时发现了这个问题