Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/357.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 如何组织数据库访问层?_Python_Database_Testing_Orm_Mocking - Fatal编程技术网

Python 如何组织数据库访问层?

Python 如何组织数据库访问层?,python,database,testing,orm,mocking,Python,Database,Testing,Orm,Mocking,我正在使用SqlAlchemy,一个python ORM库。我曾经通过调用SqlAlchemy API直接从业务层访问数据库 但后来我发现这会导致运行所有测试用例的时间过长,现在我想我应该创建一个DB访问层,这样我可以在测试期间使用模拟对象,而不是直接访问数据库 我认为有两种选择: 使用一个包含DB连接的类和许多方法,如addUser/delUser/updateUser、addBook/delBook/updateBook。但这意味着这个班级将非常大 另一种方法是创建不同的管理器类,如“Use

我正在使用SqlAlchemy,一个python ORM库。我曾经通过调用SqlAlchemy API直接从业务层访问数据库

但后来我发现这会导致运行所有测试用例的时间过长,现在我想我应该创建一个DB访问层,这样我可以在测试期间使用模拟对象,而不是直接访问数据库

我认为有两种选择:

  • 使用一个包含DB连接的类和许多方法,如addUser/delUser/updateUser、addBook/delBook/updateBook。但这意味着这个班级将非常大

  • 另一种方法是创建不同的管理器类,如“UserManager”、“BookManager”。但这意味着我必须将经理列表传递给业务层,这似乎有点麻烦


  • 如何组织数据库层?

    我会在测试期间设置一个数据库连接,改为连接内存中的数据库。像这样:

    sqlite_memory_db = create_engine('sqlite://')
    

    这将是你能得到的最快的速度,你也没有连接到一个真正的数据库,只是内存中的一个临时数据库,所以你不必担心测试后剩下的测试所做的更改,等等,你不必嘲笑任何东西。

    SQLAlchemy有一些功能——也许这比试图重写项目的整个部分更容易

    这是个好问题
    这个问题并非微不足道,可能需要几种方法来解决。 例如:

  • 组织代码,以便您可以在不访问数据库的情况下测试大多数应用程序逻辑。这意味着每个类都有访问数据的方法和处理数据的方法,第二个类可以很容易地进行测试
  • 当您需要测试数据库访问时,可以使用代理(因此,与解决方案#1类似);您可以将其视为SqlAlchemy的引擎或SA的替代品。在这两种情况下,你可能都想思考一个问题
  • 如果代码不涉及存储过程,请考虑使用内存中的数据库,如Lennart所说(即使在这种情况下,将其称为“单元测试”可能听起来有点奇怪!)
  • 然而,从我的经验来看,一切都是很容易的话,然后突然下降,当你去外地。例如,当大多数逻辑都在SQL语句中时,该怎么办?如果访问数据与数据处理严格交错,该怎么办?有时您可能能够重构,有时(特别是对于大型和遗留应用程序)无法重构

    最后,我认为这主要是一个思维方式的问题 如果您认为需要进行单元测试,并且需要让它们快速运行,那么您可以以某种方式设计应用程序,从而简化单元测试。

    不幸的是,这并不总是正确的(许多人认为单元测试可以在一夜之间运行,所以时间不是问题),并且您得到的东西将不是真正的单元测试。

    一种捕获对数据库的修改的方法,是使用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'
    

    但无论如何,您至少需要对数据库进行一些测试,因为您至少需要知道查询是否按预期工作。请记住,您还可以通过
    会话.update()
    删除()
    执行()

    对数据库进行修改。您好,因为我的一些同事坚持我们应该使用存储过程,而我对此没有控制权,所以sqlite对我来说不是一个可能的选择。顺便说一句:sqlite不支持storage procedure.Hm。这意味着您必须模拟代码的重要部分(存储过程)。这将使测试变得不那么有用。棘手的情况。谢谢@brool,但该链接现在已断开:(