我正在使用SqlAlchemy,一个python ORM库。我曾经通过调用SqlAlchemy API直接从业务层访问数据库。
但后来我发现这会导致我运行所有测试用例的时间过长,现在我想也许我应该创建一个数据库访问层,所以我可以在测试期间使用模拟对象而不是直接访问数据库。
我认为有两种选择:
使用包含数据库连接的单个类以及addUser / delUser / updateUser,addBook / delBook / updateBook等许多方法。但这意味着类会非常大。
另一种方法是创建不同的管理器类,如“UserManager”,“BookManager”。但这意味着我必须将经理列表传递给业务层,这看起来有点麻烦。
您将如何组织数据库层?
答案 0 :(得分:5)
这是一个很好的问题!
问题不是微不足道的,可能需要几种方法来解决它。
例如:
但是,从我的经验来看,一切都很容易,然后当你去场时突然下降。例如,当大多数逻辑在SQL语句中时该怎么办?如果访问数据与其处理严格交错怎么办?有时你可能有时(特别是对于大型和遗留应用程序)重构。
最后,我认为这主要是心态的问题
如果您认为需要进行单元测试,并且需要让它们快速运行,那么您可以通过某种方式设计应用程序,以便更轻松地进行单元测试。
不幸的是,这并不总是正确的(许多人认为单元测试可以在一夜之间运行,因此时间不是问题),并且你得到的东西不是真正的单元可测试的。
答案 1 :(得分:2)
我会在测试期间建立一个连接到内存数据库的数据库连接。像这样:
sqlite_memory_db = create_engine('sqlite://')
这将是你可以获得的速度,你也没有连接到真正的数据库,而只是内存中的临时数据库,所以你不必担心你的测试后所做的更改测试,等等。你不必嘲笑任何东西。
答案 2 :(得分:2)
捕获对数据库的修改的一种方法是使用SQLAlchemy会话扩展机制并使用以下内容拦截对数据库的刷新:
from sqlalchemy.orm.attributes import instance_state
from sqlalchemy.orm import SessionExtension
class MockExtension(SessionExtension):
def __init__(self):
self.clear()
def clear(self):
self.updates = set()
self.inserts = set()
self.deletes = set()
def before_flush(self, session, flush_context, instances):
for obj in session.dirty:
self.updates.add(obj)
state = instance_state(obj)
state.commit_all({})
session.identity_map._mutable_attrs.discard(state)
session.identity_map._modified.discard(state)
for obj in session.deleted:
self.deletes.add(obj)
session.expunge(obj)
self.inserts.update(session.new)
session._new = {}
然后,对于测试,您可以使用该模拟配置会话,并查看它是否符合您的期望。
mock = MockExtension()
Session = sessionmaker(extension=[mock], expire_on_commit=False)
def do_something(attr):
session = Session()
obj = session.query(Cls).first()
obj.attr = attr
session.commit()
def test_something():
mock.clear()
do_something('foobar')
assert len(mock.updates) == 1
updated_obj = mock.updates.pop()
assert updated_obj.attr == 'foobar'
但是你至少要对数据库进行一些测试,因为你至少想知道你的查询是否按预期工作。请注意,您还可以通过session.update()
,.delete()
和.execute()
对数据库进行修改。
答案 3 :(得分:0)
SQLAlchemy为making mocking easier提供了一些功能 - 也许这比尝试重写项目的整个部分更容易?