Django:运行测试时如何查看 SQL 查询?

问题描述 投票:0回答:12

我的一个 Django 应用程序单元测试失败了

DatabaseError: ORA-00942: table or view does not exist

我想查看导致此错误的实际 SQL 查询。你知道如何实现这一目标吗?

sql django oracle unit-testing django-testing
12个回答
37
投票

另一种选择是使用

CaptureQueriesContext
(使用
pytest
进行测试)。

from django.db import connection
from django.test.utils import CaptureQueriesContext


def test_foo():
    with CaptureQueriesContext(connection) as ctx:
        # code that runs SQL queries
        print(ctx.captured_queries)

来源:


19
投票

如果您想打印/记录测试中的所有 SQL 查询,请尝试像这样子类化

TestCase

from django.conf import settings
from django.template import Template, Context
import sys
from django.db import connection
from django.test import TestCase

class LoggingTestCase(TestCase):

  @staticmethod
  def setUpClass():
    # The test runner sets DEBUG to False. Set to True to enable SQL logging.
    settings.DEBUG = True
    super(LoggingTestCase, LoggingTestCase).setUpClass()

  @staticmethod
  def tearDownClass():
    super(LoggingTestCase, LoggingTestCase).tearDownClass()

    time = sum([float(q['time']) for q in connection.queries])
    t = Template("{{count}} quer{{count|pluralize:\"y,ies\"}} in {{time}} seconds:\n\n{% for sql in sqllog %}[{{forloop.counter}}] {{sql.time}}s: {{sql.sql|safe}}{% if not forloop.last %}\n\n{% endif %}{% endfor %}")
    print >> sys.stderr, t.render(Context({'sqllog': connection.queries, 'count': len(connection.queries), 'time': time}))

    # Empty the query list between TestCases.    
    connection.queries = []

然后使用

LoggingTestCase
而不是
TestCase
作为测试中的基类。如果您覆盖它,请记住调用它
tearDownClass


9
投票

您还可以执行以下操作来获取查询(然后例如打印它或在测试中评估它)。

其实现在你不应该改变

django.conf.settings
,所以我用
override_settings

from django.db import connection, reset_queries
from django.test import override_settings, TransactionTestCase

class TransactionTests(TransactionTestCase):

    @override_settings(DEBUG=True)
    def test_sql(self):
        reset_queries()
        try:
            # Code that uses the ORM goes here
        except Exception as e:
            pass
        self.assertEqual(connection.queries, [])

TestCase
也可能合适,请参阅此答案中的差异。

有关 SQL 输出的详细信息,请参阅 Django 文档


6
投票

另一种选择是在测试中使用

connection.execute_wrapper()
,如下所示:

from django.db import connection

def logger(execute, sql, params, many, context):
    print(sql, params)
    return execute(sql, params, many, context)

class GizmoTest(TestCase):

    def test_with_sql_logging(self):
        with connection.execute_wrapper(logger):
            code_that_uses_database()

使用 Django 2.2 进行测试。


2
投票

这不是最干净的解决方案,但如果您只是想快速调试而不安装额外的包,您可以在 django/db 中查找execute()方法。

对于 Oracle 我猜它在:

django/db/backends/oracle/base.py 并查找:

def execute

对于 PostgreSQL 它位于:

django/db/backends/postgresql_psycopg2/base.py

在CursorWrapper中有一个execute()方法。

两者都捕获 IntegrityError 和 DatabaseError,您可以在那里添加打印语句。

对于想要查看所有 sql 查询的人,请将 print 语句放在函数调用之后。


1
投票

对于

pytest
pytest-django
,只需为其创建一个夹具

@pytest.fixture
def debug_queries(db):
    """ Because pytest run tests with DEBUG=False
        the regular query logging will not work, use this fixture instead
    """
    from django.db import connection
    from django.test.utils import CaptureQueriesContext
    with CaptureQueriesContext(connection):
        yield connection

然后在你的测试中

@pytest.mark.django_db
def test__queries(debug_queries):
    # run your queries here

当然,您的日志记录配置应该启用查询日志记录,如下所示:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '%(asctime)s - %(levelname)s - %(name)s - %(message)s',
        },
    },
    'handlers': {
        'default': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'standard',
            'stream': 'ext://sys.stdout',
        },
    },
    'loggers': {
        'django.db.backends': {
            'level': 'DEBUG',
            'handlers': ['default'],
            'propagate': False,
        },
    }
}

1
投票

这是对我有用的解决方案(Django 3.1):

from django.test import TestCase


class TestSomething(TestCase):
    @override_settings(DEBUG=True)
    def test_something(self):
        pass
    
    def tearDown(self):
        from django.db import connection
        for query in connection.queries:
            print(f"✅ {query['sql']}\n")

来源


0
投票

到目前为止我发现的最好的解决方案是 django-debugtoolbar 提供的 debugsqlshell 自定义 django 管理命令。


0
投票

您可以在设置中将控制台级别更改为DEBUG。 它适用于 Django 1.9。

LOGGING = {
...
'handlers': {
    'console': {
        'level': 'DEBUG',
        'class': 'logging.StreamHandler',
        'formatter': 'simple'
        },
    }
...
}

0
投票

这里的所有选项都太复杂/太多的事情可能会出错。这适用于

django
>=
1.11+
<=
4.x
[在
main
分支上测试它](直到他们将来破坏它..)

它的工作原理是完全忽略

settings.DEBUG
并仅注入始终使用的
CursorDebugWrapper
,从而记录运行的 SQL。

import inspect
from collections import deque
from contextlib import contextmanager
from unittest import mock

from django.db import connections
from django.db.backends import utils


@contextmanager
def print_queries(using="default"):
    """
    [debug] Prints out all the queries in real time

    To avoid messing with django's logging and get the SQL working inside
    tests where `DEBUG` can be set to `False`, this bypasses all that and
    goes straight for the kill.

    Example:

        class TestSomething(TestCase):
            def test_something(self):
                with print_queries():
                    Model.objects.create(a=1, b=2)

        def test_something():
            with print_queries():
                Model.objects.create(a=1, b=2)

    """

    def fake_maker(self, cursor):
        """
        Normally `make_cursor` uses `CursorWrapper` which does NOT debug.
        """
        return utils.CursorDebugWrapper(cursor, self)

    class Snitch(deque):
        """
        Modified version of `deque` that `print()`s out all the items inserted to it.
        """

        def append(self, item):
            current_frame = inspect.currentframe().f_back
            while True:
                info = inspect.getframeinfo(current_frame)
                if "/django/" in info.filename:
                    current_frame = current_frame.f_back
                    continue
                break

            print("*", item["sql"], item["time"], f"{info.filename}:{info.lineno}")
            return super().append(item)

    connection_ = connections[using]
    mock_maker = mock.patch("django.db.backends.base.base.BaseDatabaseWrapper.make_cursor", fake_maker)
    mock_deque = mock.patch.object(connection_, "queries_log", Snitch(maxlen=connection_.queries_log.maxlen))

    with mock_maker, mock_deque:
        yield

像这样使用它:

def test_whatever():
    ...
    with print_queries():
        Model.objects.create(a=1, b=2)  # any query here
    ...

输出看起来像:

* SELECT .. FROM "table" WHERE ... 0.001 /full/path/file.py:136
* SELECT .. FROM "table" WHERE ... 0.001 /full/path/file.py:245

会告诉您在代码中的哪个位置进行查询。


0
投票

我将几个现有的答案组合到一个实用函数中,我发现在调试单个测试时很方便:

import traceback
from django.db import connections

@contextmanager
def log_queries():
    """
    Prints queries and their tracebacks when they are executed.

    Usage:
    
        with log_queries():
            do_something_with_the_database()
    """

    db = connections["default"]

    def print_stack_in_project(
        execute, sql, params, many, context
    ):
        try:
            return execute(sql, params, many, context)
        finally:
            query = db.queries_log[-1]
            print(f"[{query['time']}] {query['sql']}")
            traceback.print_stack()
            print()  # to separate stacks

    with db.execute_wrapper(print_stack_in_project):
        yield

0
投票

您也可以只使用记录器

将其粘贴到您的根目录中

conftest.py

@pytest.fixture#(autouse=True)
def debug_true(settings):
    import logging
    dblog = logging.getLogger('django.db.backends')
    dblog.setLevel(logging.DEBUG)
    dblog.propagate = False
    dblog.addHandler(logging.StreamHandler())
    settings.DEBUG = True
© www.soinside.com 2019 - 2024. All rights reserved.