如何正确地 运行 连续测试查询 Flask-SQLAlchemy 数据库?
How to properly run consecutive tests querying a Flask-SQLAlchemy database?
我正在为使用 SQLAlchemy 作为 ORM 的 Flask 项目设置单元测试。对于我的测试,我需要在每次 运行 单个单元测试时设置一个新的测试数据库。不知何故,我似乎无法 运行 查询数据库的连续测试,即使我 运行 这些测试是孤立的,它们也会成功。
我使用 flask-testing
包,并遵循他们的文档 here。
这是一个说明问题的工作示例:
app.py:
from flask import Flask
def create_app():
app = Flask(__name__)
return app
if __name__ == '__main__':
app = create_app()
app.run(port=8080)
database.py:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
models.py:
from database import db
class TestModel(db.Model):
"""Model for testing."""
__tablename__ = 'test_models'
id = db.Column(db.Integer,
primary_key=True
)
test/__init__.py:
from flask_testing import TestCase
from app import create_app
from database import db
class BaseTestCase(TestCase):
def create_app(self):
app = create_app()
app.config.update({
'SQLALCHEMY_DATABASE_URI': 'sqlite:///:memory:',
'SQLALCHEMY_TRACK_MODIFICATIONS': False,
'TESTING': True
})
db.init_app(app)
return app
def setUp(self):
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
test/test_app.py:
from models import TestModel
from test import BaseTestCase
from database import db
test_model = TestModel()
class TestApp(BaseTestCase):
"""WebpageEnricherController integration test stubs"""
def _add_to_db(self, record):
db.session.add(record)
db.session.commit()
self.assertTrue(record in db.session)
def test_first(self):
"""
This test runs perfectly fine
"""
self._add_to_db(test_model)
result = db.session.query(TestModel).first()
self.assertIsNotNone(result, 'Nothing in the database')
def test_second(self):
"""
This test runs fine in isolation, but fails if run consecutively
after the first test
"""
self._add_to_db(test_model)
result = db.session.query(TestModel).first()
self.assertIsNotNone(result, 'Nothing in the database')
if __name__ == '__main__':
import unittest
unittest.main()
所以,如果 运行 孤立,我可以 运行 TestApp.test_first
和 TestApp.test_second
很好。如果我连续 运行 它们,第一个测试通过,但第二个测试失败:
=================================== FAILURES ===================================
_____________________________ TestApp.test_second ______________________________
self = <test.test_app.TestApp testMethod=test_second>
def test_second(self):
"""
This test runs fine in isolation, but fails if run consecutively
after the first test
"""
self._add_to_db(test_model)
result = db.session.query(TestModel).first()
> self.assertIsNotNone(result, 'Nothing in the database')
E AssertionError: unexpectedly None : Nothing in the database
数据库设置和拆卸过程中出了点问题,但我不知道是什么原因。我该如何正确设置?
答案是,您通过重复使用在模块范围 (test_model = TestModel()
) 中定义一次的单个 TestModel
实例,在一个测试和下一个测试之间泄漏状态。
该实例在第一个测试开始时的状态是 transient
:
an instance that’s not in a session, and is not saved to the database;
i.e. it has no database identity. The only relationship such an object
has to the ORM is that its class has a mapper() associated with it.
第二次测试开始时对象的状态是detached
:
Detached - an instance which corresponds, or previously corresponded,
to a record in the database, but is not currently in any session. The
detached object will contain a database identity marker, however
because it is not associated with a session, it is unknown whether or
not this database identity actually exists in a target database.
Detached objects are safe to use normally, except that they have no
ability to load unloaded attributes or attributes that were previously
marked as “expired”.
测试之间的这种相互依赖几乎总是一个坏主意。您可以在每次测试结束时对对象使用 make_transient()
:
class BaseTestCase(TestCase):
...
def tearDown(self):
db.session.remove()
db.drop_all()
make_transient(test_model)
或者您应该为每个测试构造一个新的 TestModel
实例:
class BaseTestCase(TestCase):
...
def setUp(self):
db.create_all()
self.test_model = TestModel()
class TestApp(BaseTestCase):
...
def test_xxxxx(self):
self._add_to_db(self.test_model)
我认为后者是更好的选择,因为在测试之间没有任何其他泄漏状态的危险。
我正在为使用 SQLAlchemy 作为 ORM 的 Flask 项目设置单元测试。对于我的测试,我需要在每次 运行 单个单元测试时设置一个新的测试数据库。不知何故,我似乎无法 运行 查询数据库的连续测试,即使我 运行 这些测试是孤立的,它们也会成功。
我使用 flask-testing
包,并遵循他们的文档 here。
这是一个说明问题的工作示例:
app.py:
from flask import Flask
def create_app():
app = Flask(__name__)
return app
if __name__ == '__main__':
app = create_app()
app.run(port=8080)
database.py:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
models.py:
from database import db
class TestModel(db.Model):
"""Model for testing."""
__tablename__ = 'test_models'
id = db.Column(db.Integer,
primary_key=True
)
test/__init__.py:
from flask_testing import TestCase
from app import create_app
from database import db
class BaseTestCase(TestCase):
def create_app(self):
app = create_app()
app.config.update({
'SQLALCHEMY_DATABASE_URI': 'sqlite:///:memory:',
'SQLALCHEMY_TRACK_MODIFICATIONS': False,
'TESTING': True
})
db.init_app(app)
return app
def setUp(self):
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
test/test_app.py:
from models import TestModel
from test import BaseTestCase
from database import db
test_model = TestModel()
class TestApp(BaseTestCase):
"""WebpageEnricherController integration test stubs"""
def _add_to_db(self, record):
db.session.add(record)
db.session.commit()
self.assertTrue(record in db.session)
def test_first(self):
"""
This test runs perfectly fine
"""
self._add_to_db(test_model)
result = db.session.query(TestModel).first()
self.assertIsNotNone(result, 'Nothing in the database')
def test_second(self):
"""
This test runs fine in isolation, but fails if run consecutively
after the first test
"""
self._add_to_db(test_model)
result = db.session.query(TestModel).first()
self.assertIsNotNone(result, 'Nothing in the database')
if __name__ == '__main__':
import unittest
unittest.main()
所以,如果 运行 孤立,我可以 运行 TestApp.test_first
和 TestApp.test_second
很好。如果我连续 运行 它们,第一个测试通过,但第二个测试失败:
=================================== FAILURES ===================================
_____________________________ TestApp.test_second ______________________________
self = <test.test_app.TestApp testMethod=test_second>
def test_second(self):
"""
This test runs fine in isolation, but fails if run consecutively
after the first test
"""
self._add_to_db(test_model)
result = db.session.query(TestModel).first()
> self.assertIsNotNone(result, 'Nothing in the database')
E AssertionError: unexpectedly None : Nothing in the database
数据库设置和拆卸过程中出了点问题,但我不知道是什么原因。我该如何正确设置?
答案是,您通过重复使用在模块范围 (test_model = TestModel()
) 中定义一次的单个 TestModel
实例,在一个测试和下一个测试之间泄漏状态。
该实例在第一个测试开始时的状态是 transient
:
an instance that’s not in a session, and is not saved to the database; i.e. it has no database identity. The only relationship such an object has to the ORM is that its class has a mapper() associated with it.
第二次测试开始时对象的状态是detached
:
Detached - an instance which corresponds, or previously corresponded, to a record in the database, but is not currently in any session. The detached object will contain a database identity marker, however because it is not associated with a session, it is unknown whether or not this database identity actually exists in a target database. Detached objects are safe to use normally, except that they have no ability to load unloaded attributes or attributes that were previously marked as “expired”.
测试之间的这种相互依赖几乎总是一个坏主意。您可以在每次测试结束时对对象使用 make_transient()
:
class BaseTestCase(TestCase):
...
def tearDown(self):
db.session.remove()
db.drop_all()
make_transient(test_model)
或者您应该为每个测试构造一个新的 TestModel
实例:
class BaseTestCase(TestCase):
...
def setUp(self):
db.create_all()
self.test_model = TestModel()
class TestApp(BaseTestCase):
...
def test_xxxxx(self):
self._add_to_db(self.test_model)
我认为后者是更好的选择,因为在测试之间没有任何其他泄漏状态的危险。