当pytest测试断言查询结果rowcount失败时,数据库操作挂起

时间:2016-03-08 10:39:07

标签: python postgresql sqlalchemy deadlock pytest

以下测试正常:

import pytest
from sqlalchemy import create_engine, text
from sqlalchemy.engine.url import URL
from sqlalchemy_utils import database_exists, create_database

@pytest.fixture()
def db_engine():
    engine = create_engine(
        URL('postgres',
            username='postgres', password='postgres', database='my_database')
    )
    if not database_exists(engine.url):
        create_database(engine.url)
    return engine


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:
            del result  # The result would block the dropping of
                        # SCHEMA "test_schema" in the following cleanup.
        except NameError:
            pass
        db_engine.execute(text('DROP SCHEMA IF EXISTS test_schema CASCADE;'))

如果我将其设为失败,则将assert result.rowcount == 0更改为assert result.rowcount == 1将无限期地挂在最后一行(架构所在的位置)被删除)甚至不能被[ Ctrl + c ]中止。我必须kill py.test进程(或python进程,具体取决于我调用测试运行器的方式)来终止它。 (如果我追加

if __name__ == "__main__":
    test_new_table_is_empty(db_engine())

并将文件作为普通的python脚本而不是py.test运行,我得到预期的AssertionError。)

但是,如果我只是用assert False替换断言(并再次使用py.test运行),那么测试套装将按照预期完成一次失败的测试。因此,我假设如果断言失败,pytest将保留对result的引用,可能是因为它与堆栈跟踪一起显示的错误分析。 是这样的吗?

我怎样才能也应该避免阻塞?我是否应该只对结果中提取的数据而不是ResultProxy本身的属性进行测试断言?

1 个答案:

答案 0 :(得分:2)

TL; DR

呼叫

result.close()

而不是

del result

您特定问题的答案:

  

我认为如果断言失败,pytest会保留对result的引用,可能是因为它与堆栈跟踪一起显示的错误分析。 是这样的吗?

这仍然是我的工作假设。如果有人知道的话,请赐教。

  

我该如何避免阻止?

而不是del ResultProxy result,而是在finally子句中明确close()

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;'))

这将release result所持有的所有行和表锁。

避免嵌套try子句的混乱将嵌套的try子句的混乱移动到其他地方,您可以使用with contextlib.closing(...):

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;'))
  

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

只有在您获取所有行时才会有效,从而耗尽ResultProxy,这会隐含_soft_close()它。如果结果(可能意外地)有更多行而不是您获取,则结果将保持打开并继续保持锁定,以防止执行以下清理。

由于您只对测试中的行数而不是实际结果的内容感兴趣,因此明确关闭是获取您不会使用的结果的最佳选择,除非可能用于计数他们或计算他们的长度。