Flask单元测试和SQLAlchemy使用所有连接

5

我最近在运行我的Flask应用程序的单元测试时遇到了一个问题,大约有100个单元测试。所有单元测试都将通过,但是当一次性运行它们时,它们会失败并显示以下错误:

OperationalError: (OperationalError) FATAL:  remaining connection slots are reserved for non-replication superuser connections

所有内容都在本地机器的virtualbox/vagrant/ubuntu12.04实例中运行。我的postgres max_connections设置为100,因此我认为连接没有关闭,在运行了100次测试后,我使用了所有可用的连接。

这个人Flask unit tests with SQLAlchemy and PostgreSQL exhausts db connections看起来遇到了完全相同的问题。Mike/Zzzeek (sqlalchemy开发者)甚至回复说可能在create_app()中发生了某些事情,因此我也将其包含在下面。

这是否意味着我没有在某个地方关闭连接?所有这些错误都是由我的unittest的setUp()方法中的db.create_all()触发的。

# test.py

class TestCase(DataMixin, Base):
    """Base test class"""

    def create_app(self):
        return create_app(TestConfig())

    def setUp(self):
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()

# app.py

def create_app(config=None):
    app = Flask(__name__)

    # Config
    app.config.from_object(BaseConfig())
    if config is not None:
        app.config.from_object(config)

    # Extensions
    db.init_app(app)
    mail.init_app(app)
    bcrypt.init_app(app)

    # Blueprints
    app.register_blueprint(core_blueprint, url_prefix='/')
    app.register_blueprint(accounts_blueprint, url_prefix='/account')
    app.register_blueprint(admin_blueprint, url_prefix='/admin')
    app.register_blueprint(cart_blueprint, url_prefix='/cart')

    # Login Manager
    login_manager.setup_app(app, add_context_processor=True)
    login_manager.login_view = "accounts.login"
    login_manager.user_callback = load_user

    # Templates
    app.jinja_env.globals['is_admin'] = is_admin
    app.jinja_env.globals['is_staff'] = is_staff

    @app.context_processor
    def inject_cart():
        cart = count = None
        if current_user.is_authenticated():
            cart = current_user.get_cart()
        return dict(cart=cart)

    # Error Handling
    @app.errorhandler(404)
    def page_not_found(error):
        return render_template('404.html'), 404

    return app

1
可能是Flask单元测试与SQLAlchemy和PostgreSQL耗尽数据库连接的重复问题。 - Dag Høidahl
2个回答

15

更新:已测试并修复

不要每次都创建新的连接和重新创建数据库(速度慢),可以使用子会话,在每次测试后执行回滚。

连接会被重复利用,这也会解决你遇到的问题。

class TestCase(Base):

    @classmethod
    def setUpClass(cls):
        cls.app = create_app(MyConfig())
        cls.client = cls.app.test_client()
        cls._ctx = cls.app.test_request_context()
        cls._ctx.push()
        db.create_all()

    @classmethod
    def tearDownClass(cls):
        db.session.remove()
        db.drop_all()
        db.get_engine(cls.app).dispose()

    def setUp(self):
        self._ctx = self.app.test_request_context()
        self._ctx.push()
        db.session.begin(subtransactions=True)

    def tearDown(self):
        db.session.rollback()
        db.session.close()
        self._ctx.pop()  

如果您需要为每个测试创建一个应用程序实例,只需将其添加到setUp方法中,但仍然保留在setUpClass中。

下面是完整的测试示例,需要flask_sqlalchemy和psycopg2。创建名为“test”的测试数据库,并将其连接限制设置为15。

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from unittest import TestCase as Base


db = SQLAlchemy()

def create_app(config=None):
    app = Flask(__name__)
    app.config.from_object(config)
    db.init_app(app)
    return app


class MyConfig(object):
    SQLALCHEMY_DATABASE_URI = "postgresql://localhost/test"
    TESTING = True


class TestCase(Base):
    @classmethod
    def setUpClass(cls):
        cls.app = create_app(MyConfig())
        cls.client = cls.app.test_client()
        cls._ctx = cls.app.test_request_context()
        cls._ctx.push()
        db.create_all()

    @classmethod
    def tearDownClass(cls):
        db.session.remove()
        db.drop_all()   

    def setUp(self):
        self._ctx = self.app.test_request_context()
        self._ctx.push()
        db.session.begin(subtransactions=True)

    def tearDown(self):
        db.session.rollback()
        db.session.close()
        self._ctx.pop()


class TestModel(TestCase):

    def test_01(self):
        pass

    def test_02(self):
        pass

    def test_03(self):
        pass

    def test_04(self):
        pass

    def test_05(self):
        pass

    def test_06(self):
        pass

    def test_07(self):
        pass

    def test_08(self):
        pass

    def test_09(self):
        pass

    def test_10(self):
        pass

    def test_11(self):
        pass

    def test_12(self):
        pass

    def test_13(self):
        pass

    def test_14(self):
        pass

    def test_15(self):
        pass

    def test_16(self):
        pass


if __name__ == "__main__":
    import unittest
    unittest.main()

setUpClass在调用create_app()之前运行,因此还没有初始化db。尽管这对单元测试的性能很有趣。即使它可以工作,我认为我的代码仍然会有一些问题没有关闭连接。这只是解决可能在生产中出现的问题的一种变通方法。 - Adam P
修复并添加了完整的示例。 - Juan-Pablo Scaletti
1
Juan-Pablo,非常感谢您抽出时间来回答我的问题。我完全不在乎积分,但我回答了自己的问题,这样下一个人就不会那么沮丧了。我相信您的解决方案可能是解决问题的更好方法,因为它解决了问题并显然使测试运行速度更快,但我也无法使其工作。我可以运行您的代码,但由于数据库没有真正回滚,所以我违反了数据库上的唯一约束条件。我将继续研究。谢谢您的时间。 - Adam P
这个问题是你不能再并行运行测试了,因为必须按顺序运行它们,因为事务不能重叠。 - TjerkW

8

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接