django 在运行测试时如何查看 sql 查询?

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

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

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

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

sql django oracle unit-testing django-testing
10个回答
29
投票

另一种选择是使用

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
投票

如果你想打印/记录 all 来自测试的 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
可能也适用,请参阅此answer 中的差异。

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


5
投票

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

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 查询的人,将打印语句放在函数调用之后。


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,
        },
    }
}

0
投票

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


0
投票

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

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

0
投票

这是对我有用的解决方案(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
>=
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

会告诉您在代码中的何处进行查询。

© www.soinside.com 2019 - 2024. All rights reserved.