1

我正在使用 SQLAlchemy 作为 ORM 为 Flask 项目设置单元测试。对于我的测试,我需要在每次运行单个单元测试时设置一个新的测试数据库。不知何故,我似乎无法运行查询数据库的连续测试,即使我单独运行这些测试它们也会成功。

我使用该软件包,并在此处flask-testing遵循他们的文档。

这是一个说明问题的工作示例:

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

数据库设置和拆卸中出了点问题,但我不知道是什么。如何正确设置?

4

1 回答 1

1

TestModel答案是通过重用在模块范围 ( test_model = TestModel())中定义的单个实例,您正在一个测试和下一个测试之间泄漏状态。

该实例在第一次测试开始时的状态是transient

不在会话中且未保存到数据库中的实例;即它没有数据库身份。这样一个对象与 ORM 的唯一关系是它的类有一个与之关联的 mapper()。

第二次测试开始时物体的状态是detached

已分离 - 对应于或先前对应于数据库中的记录但当前不在任何会话中的实例。分离的对象将包含一个数据库身份标记,但是由于它与会话无关,因此不知道此数据库身份是否实际存在于目标数据库中。分离的对象可以安全地正常使用,只是它们无法加载已卸载的属性或之前标记为“过期”的属性。

这种测试之间的相互依赖几乎总是一个坏主意。您可以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)

我认为后者是更好的选择,因为在测试之间不存在任何其他泄漏状态的危险。

于 2019-05-07T01:19:17.657 回答