【问题标题】:How can I test a Flask application which uses SQLAlchemy?如何测试使用 SQLAlchemy 的 Flask 应用程序?
【发布时间】:2013-07-21 10:10:20
【问题描述】:

我在 Flask 上有一个使用 SqlAlchemy 来审核新闻的 Web 应用程序,它有一些 api 方法来处理审核请求,例如批准、拒绝当前选定的新闻、列出它们等。

我想为此方法编写单元测试,并让它们工作,但我不明白如何在一个数据库会话中执行我从测试用例执行的所有请求,以便我可以删除所有更改数据库。还是有其他更清洁或正确的方法来做到这一点?

我发现也许我需要的只是 SqlAlchemy 中的“scoped_session”,但我实现它的所有尝试都失败了。如果这是正确的方法,请告诉我在哪里使用这行代码(在设置中,或在测试用例 set_up 方法中)。

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker
session_factory = sessionmaker()
Session = scoped_session(session_factory) 

【问题讨论】:

  • 你是直接使用sqlalchemy还是flask-sqlalchemy扩展?

标签: python session testing flask sqlalchemy


【解决方案1】:

我建议您使用Flask-Testing 扩展名。这是一个经过批准的扩展,可让您根据需要进行单元测试。它也有专门针对 SQLAlchemy 的部分。

使用 SQLAlchemy 进行测试

如果您将 Flask-Testing 与 SQLAlchemy 一起使用,这将涵盖几个要点。假设您使用的是 Flask-SQLAlchemy 扩展,但如果不是这样,示例应该不会太难适应您自己的特定设置。

首先,确保将数据库 URI 设置为生产数据库以外的其他内容!其次,在每次测试运行时创建和删除表通常是个好主意,以确保干净的测试:"

from flask.ext.testing import TestCase

from myapp import create_app, db

class MyTest(TestCase):

    SQLALCHEMY_DATABASE_URI = "sqlite://"
    TESTING = True

    def create_app(self):

        # pass in test configuration
        return create_app(self)

    def setUp(self):

        db.create_all()

    def tearDown(self):

        db.session.remove()
        db.drop_all()

【讨论】:

  • 鉴于对这个问题的支持,我一定遗漏了一些东西,但create_app 的实现在这里不是很重要吗?链接的文档也没有解释 myapp.create_app 应该做什么(例如,与数据库有关)。
  • create_app 我认为应该像在你的 app.py 中一样返回应用程序——例如应用程序 = 烧瓶(名称)。因为我不想为测试和生产使用不同的配置,所以我只是从我的主命名空间导入应用程序,然后在 create_app 中返回它,它工作正常。
  • @ForeverWintr 这是一个 wsgi 应用程序工厂,它是一种常见的烧瓶模式,用于简化测试和中间件实现。可以表示为:create_app = lambda: flask.Flask(__name__)
【解决方案2】:

这是我最近运行单元测试的方式。我假设因为您使用的是 SQLAlchemy,所以您正在使用模型类。我还假设您的所有表都定义为 SQLAlchemy 模型类。

from flask import Flask
import unittest

from app import db
from app.models import Log
from constants import test_logs

class appDBTests(unittest.TestCase):

    def setUp(self):
        """
        Creates a new database for the unit test to use
        """
        self.app = Flask(__name__)
        db.init_app(self.app)
        with self.app.app_context():
            db.create_all()
            self.populate_db() # Your function that adds test data.

    def tearDown(self):
        """
        Ensures that the database is emptied for next unit test
        """
        self.app = Flask(__name__)
        db.init_app(self.app)
        with self.app.app_context():
            db.drop_all()

由于您使用与应用相同的数据库设置,因此您可以在运行的每个单元测试中构建和销毁测试数据库。

【讨论】:

  • 这里的LimboDBTests 是什么?
  • 项目名称是 Limbo,我显然不擅长识别旧名称。
  • 为什么self.app = Flask(__name__) 又在 tearDown() 中?我认为你在 setUp() 中设置后不需要再这样做了。
  • 这个答案在我看来是最好的。你实际上使用了一个测试数据库,而不是在生命系统上运行它,你也不必编辑你的生产代码来设置一个测试数据库(来自@user2659443和@codegeek的'create_test_app'发生了什么)
【解决方案3】:

关于 codegeek 使用 Flask-Testing 的回答,我很难理解 createapp() 的作用。 Flask-SqlAlchemy Introduction into Contexts 为我提供了一些关于如何将 SQLAlchemy 对象动态绑定到您的应用程序的指针。在这种情况下,绑定到测试应用程序。

基本上:

  1. 创建flask sqlalchemy 对象但不传入app 对象
  2. 在 create_app 函数中,创建您的测试应用程序,并动态绑定 SQLAlchemy。

您的 myapp.py

# don't pass in the app object yet
db = SQLAlchemy()

def create_test_app():
    app = Flask(__name__)
    app.config['TESTING'] = True
    app.config["SQLALCHEMY_DATABASE_URI"] = "xxxxxxtestdatabasexxx"
    # Dynamically bind SQLAlchemy to application
    db.init_app(app)
    app.app_context().push() # this does the binding
    return app

# you can create another app context here, say for production
def create_production_app():
    app = Flask(__name__)
    app.config["SQLALCHEMY_DATABASE_URI"] = "xxxxxxproductionxxxx"
    # Dynamically bind SQLAlchemy to application
    db.init_app(app)
    app.app_context().push()
    return app

然后您可以按照 Flask-Test 文档中概述的 codegeek 的解决方案进行操作

from flask.ext.testing import TestCase
from myapp import create_app, db

class MyTest(TestCase):

    # I removed some config passing here
    def create_app(self):
        return create_test_app()

    def setUp(self):

        db.create_all()

    def tearDown(self):

        db.session.remove()
        db.drop_all()

这个解决方案的好处是您可以创建不同的应用程序并使用函数动态绑定 SQLAlchemy 对象。每个应用程序可以服务于不同的目的。例如,一个用于生产,一个用于单元测试。在生产的情况下,您可以在顶级烧瓶应用程序中调用 create_production_application()。

【讨论】:

  • 在不同的TestCase重新初始化我的数据库失败后,您必须在tearDown中调用app_context.pop()才能完成重置。
猜你喜欢
  • 2011-06-28
  • 1970-01-01
  • 1970-01-01
  • 2019-02-08
  • 1970-01-01
  • 2013-01-21
  • 2021-04-29
  • 2015-06-16
  • 1970-01-01
相关资源
最近更新 更多