获取测试套件中每个测试的 SQL 查询计数概览

Ing*_*her 4 python django pytest pytest-django

我有一个大型 Django 应用程序,其中包含大量需要 SQL 查询优化的测试。

我正在使用 pytest-django 来运行我的测试。

我不想单独添加assertNumQueriesdjango-assert-num-queries针对每个测试,而是生成有关所有测试中的每个测试触发了多少 SQL 查询的概述,以了解哪些代码需要最优化,如下所示:

test                         | number of queries
------------------------------------------------
test_a.py::test_my_function1 |  5
test_a.py::test_my_function3 |  2
test_a.py::test_my_function5 |  7
Run Code Online (Sandbox Code Playgroud)

是否可以在 conftest.py 中配置一个 pytest 钩子,它计算每个 (DB) 测试的 SQL 查询数量并在结果中显示它们 - 无需修改我的测试源(如添加装饰器)?

我天真的方法是使用这些钩子,并以某种方式在每次测试前后访问数据库连接:

def pytest_runtest_call(item):
    pass

def pytest_runtest_teardown(item, nextitem):
    return True

Run Code Online (Sandbox Code Playgroud)

hoe*_*ing 5

为了记录查询计数,自动使用装置就足够了。在下面的示例中,我将计数存储在queries_count配置对象下的字典中:

@pytest.fixture(autouse=True)
def record_query_count(request):
    from django.test.utils import CaptureQueriesContext
    from django.db import connection

    with CaptureQueriesContext(connection) as context:
        yield
    num_performed = len(context)

    if not hasattr(request.config, "query_counts"):
        request.config.query_counts = dict()
    request.config.query_counts[request.node.nodeid] = num_performed
Run Code Online (Sandbox Code Playgroud)

为了输出结果,我在pytest_terminal_summary钩子的自定义实现中添加了一个自定义部分。将以下代码放入conftest.py项目或测试根目录中命名的文件中:

import os

def pytest_terminal_summary(terminalreporter, exitstatus, config):
    content = os.linesep.join(
        f'{nodeid:40} | {num_queries}'
        for nodeid, num_queries in config.query_counts.items()
    )
    terminalreporter.ensure_newline()
    terminalreporter.section('number of queries', sep='-', bold=True)
    terminalreporter.line(content)
Run Code Online (Sandbox Code Playgroud)

运行测试现在将产生:

tests/test_db.py ...                                                                [100%]

----------------------------------- number of queries ------------------------------------
tests/test_db.py::test_add_entry      | 2
tests/test_db.py::test_remove_entry   | 2
tests/test_db.py::test_no_entries     | 1
=================================== 3 passed in 0.26s ====================================
Run Code Online (Sandbox Code Playgroud)