Как настроить приложение Flask с SQLAlchemy для тестирования? - PullRequest
26 голосов
/ 17 февраля 2011

Кажется обычной практикой во 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)

А затем импортируйте и используйте app и db везде. Но когда вы создаете db, как это, он получает конфигурацию из приложения, и кажется, что эта конфигурация не может быть переопределена, как только это произойдет. На веб-сайте Flask есть несколько страниц, посвященных созданию фабрик приложений, но неясно, как я мог бы по-прежнему использовать app и db везде, если бы я это сделал.

Как мне написать скрипт для тестирования моего приложения Flask с другой базой данных? Как мне структурировать свое приложение, чтобы сделать это возможным? Должен ли я использовать module s?

Ответы [ 3 ]

11 голосов
/ 03 октября 2012

Ваш инстинкт использования переменных окружения верен.Тем не менее, существует опасность запуска модульных тестов с неправильной базой данных.Кроме того, вы можете не хотеть connect_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://"

и

# config/postgres.py
SQLALCHEMY_DATABASE_URI = 'postgresql://user:pw@localhost/somedb'

ТакЯ могу явно установить БД в моем базовом TestCase:

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/__init__.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)

и

# shell.py
import os
os.environ["DIAG_CONFIG_MODULE"] = "config.postgres"

from main import app, db
from main.symdiag.models import *
from main.auth.models import *
print sorted(k for k in locals().keys() if not k.startswith("_"))
import IPython
IPython.embed()

Может быть .. лучше пока: P.

6 голосов
/ 20 февраля 2011

Вы не захотите, чтобы соединение с БД происходило во время импорта. Начните настройку приложения во время импорта, поскольку вы всегда можете настроить конфигурацию в своих тестах, прежде чем пытаться протестировать или запустить приложение. В приведенном ниже примере у вас есть соединение с базой данных за некоторыми функциями, которые используют конфигурацию приложения, поэтому на тесте юнитов вы можете фактически изменить соединение с базой данных, чтобы оно указывало на другой файл, а затем перейти к явному соединению в вашей настройке.

Скажем, у вас есть пакет myapp, содержащий myapp.py, который выглядит следующим образом:

# 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_myapp.py будет выглядеть следующим образом:

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_db и init_db, но, надеюсь, вы поймете эту идею.

3 голосов
/ 18 января 2014

Во-первых, вместо создания экземпляра приложения Flask непосредственно в вашем скрипте, вы используете фабрику приложений . Это означает, что вы создаете функцию, которая принимает ваш конфигурационный файл в качестве параметра и возвращает экземпляр объекта приложения. Затем вы создаете глобальный объект SQLAlchemy без параметра и настраиваете его при создании приложения , как описано здесь .

db = SQLAlchemy()

def create_app(configfile):
    app = Flask(__name__)

    app.config.from_pyfile(config, silent=True)
    db.init_app(app)

    # create routes, etc.

    return app

Чтобы запустить приложение, вы просто делаете что-то вроде:

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 напрямую с scoped_session вместо Flask-SQLAlchemy. Я сделал то же самое, но с Lazy SQLAlchemy setup .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...