Python 当pytest断言查询结果行计数失败时,数据库操作挂起

Python 当pytest断言查询结果行计数失败时,数据库操作挂起,python,postgresql,sqlalchemy,deadlock,pytest,Python,Postgresql,Sqlalchemy,Deadlock,Pytest,以下测试工作正常: 导入pytest 从sqlalchemy导入创建引擎,文本 从sqlalchemy.engine.url导入url 从sqlalchemy_utils导入数据库_存在,创建数据库 @pytest.fixture def db_发动机: 引擎=创建引擎 网址'postgres', 用户名='postgres',密码='postgres',数据库='my_database' 如果不是数据库_existsengine.url: 创建_databaseengine.url 回程发动机

以下测试工作正常:

导入pytest 从sqlalchemy导入创建引擎,文本 从sqlalchemy.engine.url导入url 从sqlalchemy_utils导入数据库_存在,创建数据库 @pytest.fixture def db_发动机: 引擎=创建引擎 网址'postgres', 用户名='postgres',密码='postgres',数据库='my_database' 如果不是数据库_existsengine.url: 创建_databaseengine.url 回程发动机 def测试新表格为空发动机: 尝试: db_engine.executetext'CREATE SCHEMA test_SCHEMA;' db_engine.executetext'CREATE TABLE test_schema.new_TABLE;' 结果=db_engine.executetext'SELECT*FROM test_schema.new_table;' 断言结果。行计数==0 最后: 尝试: del result该结果将阻止 架构测试\u架构在以下清理中。 除名称错误外: 通过 db_engine.executetext“如果存在,则删除架构测试_架构级联;” 但是,如果我通过将assert result.rowcount==0更改为assert result.rowcount==1使其失败,它将无限期地挂起在应该删除架构的最后一行,甚至不能被[Ctrl+c]中止。我必须终止py.test进程或python进程,这取决于我调用测试运行程序终止它的方式。如果我附加

如果uuuu name uuuuu==\uuuuuuuu main\uuuuuuuu: 测试新表是空的 并将该文件作为普通python脚本运行,而不是通过py.test,我得到了预期的断言错误

但是,如果我只是将断言替换为assert False并再次使用py.test运行,那么测试套件将以一个失败的测试结束,正如预期的那样。因此,我假设pytest在断言失败时保留对result的引用,可能是因为它显示了堆栈跟踪的错误分析。是这样吗

我应该如何避免阻塞?我是否应该只对从结果中获取的数据进行测试断言,而不是对其本身的属性进行测试断言?

TL;博士 召唤

而不是

del result
回答您的具体问题: 我假设pytest在断言失败时保留了对result的引用,可能是因为它显示了堆栈跟踪的错误分析。是这样吗

这仍然是我的工作假设。如果有人知道的更好,请告诉我

我应该如何避免阻塞

不要删除ResultProxy结果,而是在finally子句中显式删除它:

这将删除result持有的所有行锁和表锁

要避免嵌套try子句的混乱,请将嵌套try子句的混乱移动到其他位置,您可以使用:

我是否应该只对从结果中获取的数据进行测试断言,而不是对其本身的属性进行测试断言

这只有在获取所有行时才起作用,从而耗尽ResultProxy,而ResultProxy会隐式地使用它。如果结果的行数可能出乎意料地超过您提取的行数,那么结果将保持打开状态,并可能继续保持锁,从而阻止执行以下清理


由于您只对测试中的行数感兴趣,而对实际结果的内容不感兴趣,因此显式关闭比获取不使用的结果更好,除了计算它们或计算它们的长度之外。

对于那些想知道的人来说:我没有使用在DB事务中包装测试的常见模式,该事务将在测试后回滚,因为测试中的真实代码会进行DB提交。
del result
def test_new_table_is_empty(db_engine):
    try:
        db_engine.execute(text('CREATE SCHEMA test_schema;'))
        db_engine.execute(text('CREATE TABLE test_schema.new_table ();'))
        result = db_engine.execute(text('SELECT * FROM test_schema.new_table;'))
        assert result.rowcount == 0
    finally:
        try:
            result.close()  # Release row and table locks.
        except NameError:
            pass
        db_engine.execute(text('DROP SCHEMA IF EXISTS test_schema CASCADE;'))
from contextlib import closing

# ...

def test_new_table_is_empty(db_engine):
    try:
        db_engine.execute(text('CREATE SCHEMA test_schema;'))
        db_engine.execute(text('CREATE TABLE test_schema.new_table ();'))
        with closing(
                db_engine.execute(text('SELECT * FROM test_schema.new_table;'))
        ) as result:
            assert result.rowcount == 0
    finally:
        db_engine.execute(text('DROP SCHEMA IF EXISTS test_schema CASCADE;'))